[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: Create a report to help us improve\ntitle: \"[BUG]: <Context of the issue>\"\nlabels: bug\nbody:\n  - type: textarea\n    id: description\n    attributes:\n      label: Issue description\n      description: |\n        Describe the issue in as much detail as possible.\n\n        Tip: You can attach images or log files by clicking this area to highlight it and then dragging files into it.\n      placeholder: |\n        Steps to reproduce with below code sample:\n        1. do thing\n        2. click...\n        3. observe behavior\n        4. see error logs below\n    validations:\n      required: true\n  - type: textarea\n    id: media\n    attributes:\n      label: Media & Screenshots\n      description: Include screenshots or video of reproduction as much as possible\n  - type: textarea\n    id: os\n    attributes:\n      label: Operating system\n      description: Which OS does your application run on?\n      value: |\n       - OS: [e.g. iOS]:\n       - Browser [e.g. chrome, safari]:\n\n       - Any other details...\n  - type: dropdown\n    id: priority\n    attributes:\n      label: Priority this issue should have\n      description: Please be realistic. If you need to elaborate on your reasoning, please use the Issue description field above.\n      options:\n        - Low (slightly annoying)\n        - Medium (should be fixed soon)\n        - High (immediate attention needed)\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Request a new feature\nlabels: [feature]\nbody:\n  - type: textarea\n    id: description\n    attributes:\n      label: Feature\n      description: A clear and concise description of what the problem is, or what feature you want to be implemented.\n      placeholder: I'm always frustrated when..., Discord has recently released..., A good addition would be...\n    validations:\n      required: true\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: Alternative solutions or implementations\n      description: A clear and concise description of any alternative solutions or features you have considered.\n  - type: textarea\n    id: additional-context\n    attributes:\n      label: Other context\n      description: Any other context, screenshots, or file uploads that help us understand your feature request.\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Issue\nCloses #[ISSUE_NUMBER]\n\n## What Changed\nBrief description of what you changed and why.\n\nExample:\n- Added JSON validation tooltip on parse error\n- Improved performance by memoizing graph component\n- Fixed bug where large JSON files caused slowdown\n\n## How to Test\nStep-by-step instructions to test the change:\n\n1. ...\n2. ...\n3. ...\n\n## Evidence\n**Screenshots or Video Required** — Choose one or both:\n\n### Screenshots\n- [ ] Before/after screenshots attached (for UI changes)\n\n### Video\n- [ ] Screen recording of the feature in action\n\n### Testing\n- [ ] Tested locally with `pnpm dev`\n- [ ] No console errors\n- [ ] Tested with large JSON files (if applicable)\n\n## Performance Impact\nAny notes on performance, re-renders, or potential concerns?\n\n- [ ] No performance impact\n- [ ] Improved performance (explain how)\n- [ ] Potential impact (explain and justify)\n\n## Additional Notes\nAnything else reviewers should know?\n\n---\n\n**Remember:** If this PR is not linked to an issue, it may be closed. Always reference an approved issue!\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy Next.js site to Pages\n\non:\n  push:\n    branches: [\"main\"]\n\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24.10.0\"\n\n      - uses: pnpm/action-setup@v4\n        name: Install pnpm\n        with:\n          version: 10.20.0\n          run_install: false\n\n      - name: Get pnpm store directory\n        shell: bash\n        run: |\n          echo \"STORE_PATH=$(pnpm store path --silent)\" >> $GITHUB_ENV\n\n      - uses: actions/cache@v5\n        name: Setup pnpm cache\n        with:\n          path: ${{ env.STORE_PATH }}\n          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-pnpm-store-\n\n      - name: Restore Turbo and Next.js cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            .turbo\n            apps/www/.next/cache\n          key: ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ hashFiles('apps/www/**.[jt]s', 'apps/www/**.[jt]sx', 'packages/**.[jt]s', 'packages/**.[jt]sx') }}\n          restore-keys: |\n            ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Build with Turborepo\n        run: pnpm run build:www\n        env:\n          NEXT_PUBLIC_GA_MEASUREMENT_ID: ${{ vars.NEXT_PUBLIC_GA_MEASUREMENT_ID }}\n\n      - name: Upload Build Artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: build-files\n          path: ./apps/www/out/_next/static/chunks\n\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v4\n        with:\n          path: './apps/www/out'\n\n  # Deployment job\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/pull-request.yml",
    "content": "name: Verify Pull Request\n\non:\n  pull_request:\n    branches: [ \"main\" ]\n\njobs:\n  cache-and-install:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Install Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"24.10.0\"\n\n      - uses: pnpm/action-setup@v4\n        name: Install pnpm\n        with:\n          version: 10.20.0\n          run_install: false\n\n      - name: Get pnpm store directory\n        shell: bash\n        run: |\n          echo \"STORE_PATH=$(pnpm store path --silent)\" >> $GITHUB_ENV\n\n      - uses: actions/cache@v5\n        name: Setup pnpm cache\n        with:\n          path: ${{ env.STORE_PATH }}\n          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-pnpm-store-\n\n      - name: Restore Turbo and Next.js cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            .turbo\n            apps/www/.next/cache\n          key: ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ hashFiles('apps/www/**.[jt]s', 'apps/www/**.[jt]sx', 'packages/**.[jt]s', 'packages/**.[jt]sx') }}\n          restore-keys: |\n            ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-\n\n      - name: Install dependencies\n        run: pnpm install --frozen-lockfile\n\n      - name: Lint\n        run: pnpm turbo run lint\n\n      - name: Build\n        run: pnpm turbo run build\n\n        \n"
  },
  {
    "path": ".gitignore",
    "content": "# Agent tooling\n.agents\n.claude\n.codex\nskills-lock.json\n\n# Package manager\nnode_modules/\n.npm-cache/\n.pnpm-store/\n.pnp*\nnpm-debug.log*\npnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Turborepo\n.turbo/\n\n# Build outputs and caches (all workspaces)\n**/.next/\n**/out/\n**/coverage/\n**/*.tsbuildinfo\n\n# OS\n.DS_Store\n"
  },
  {
    "path": ".npmrc",
    "content": "engine-strict=true\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Run VSCode Extension\",\n      \"type\": \"extensionHost\",\n      \"request\": \"launch\",\n      \"args\": [\"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode\"],\n      \"outFiles\": [\"${workspaceFolder}/apps/vscode/build/**/*.js\"],\n      \"preLaunchTask\": \"build vscode extension\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"label\": \"build vscode extension\",\n      \"type\": \"shell\",\n      \"command\": \"pnpm run build:vscode\",\n      \"group\": {\n        \"kind\": \"build\",\n        \"isDefault\": true\n      }\n    },\n    {\n      \"label\": \"watch vscode extension\",\n      \"type\": \"shell\",\n      \"command\": \"pnpm run dev:vscode\",\n      \"isBackground\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "\n# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nopensource@github.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of\nactions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to JSON Crack\n\nThank you for wanting to contribute! This is a community-driven project, and we appreciate your help. Please read this guide carefully to make the review process smooth and fast.\n\n**Read our [Code of Conduct](./CODE_OF_CONDUCT.md) first** — we want to keep this community friendly and welcoming.\n\n---\n\n## Before You Start: The Issue-First Workflow\n\n**Always open or find an issue BEFORE you start coding.** This saves everyone time.\n\n1. **Check existing issues** — Search to see if someone already reported this or is working on it\n2. **Open a new issue** if one doesn't exist — Describe what you want to fix or build\n3. **Wait for approval** — I'll review and give feedback (usually within a few days)\n4. **Once approved**, you can start coding\n5. **Link your PR to the issue** — Use `Closes #123` in your PR description\n\nThis workflow prevents duplicate work and ensures your contribution aligns with the project's direction.\n\n---\n\n## Quick Setup\n\n### Prerequisites\n- Node.js 18+\n- pnpm (or npm/yarn)\n\n### Tech Stack\nJSON Crack uses:\n- **React** — UI library\n- **Reaflow** — Graph visualization\n- **Mantine UI** — UI components\n- **Zustand** — State management\n\n### Get Started\n```bash\n# Clone the repo\ngit clone https://github.com/AykutSarac/jsoncrack.com.git\ncd jsoncrack.com\n\n# Install dependencies\npnpm install\n\n# Run the dev server\npnpm dev\n```\n\nThe app will be available at `http://localhost:3000`\n\n---\n\n## How to Submit a Pull Request\n\n### Requirements\nBefore submitting, make sure your PR includes:\n\n1. **Issue ID** — Reference the issue: `Closes #123`\n2. **Clear description** — What does this change do? Why?\n3. **Evidence of working changes** — One or both:\n   - **Screenshot** — Show the UI before/after\n   - **Video** — Screen recording of the feature in action\n4. **Test it locally** — Run `pnpm dev` and verify it works\n5. **Follow code style** — Use [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html)\n\n### Creating Your Branch\n```bash\ngit checkout -b fix/issue-123-description\n# or\ngit checkout -b feature/issue-123-description\n```\n\nUse clear branch names that reference the issue.\n\n---\n\n## Guidelines\n\n### Performance First\n- Avoid unnecessary re-renders\n- Use React DevTools Profiler to check performance\n- Test with large JSON files to ensure no slowdowns\n\n### Code Quality\n- Follow the [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html)\n- Write descriptive commit messages\n- Keep changes focused — one feature/fix per PR\n\n### Testing\n- Manually test your changes thoroughly\n- Describe exactly how you tested it in the PR\n- Make sure existing features still work\n\n---\n\n## Example PR\n\nHere's what a good PR looks like:\n\n**Title:** Add JSON validation tooltip on parse error\n\n**Description:**\n```\nCloses #234\n\n## What Changed\nAdded a helpful tooltip that shows validation errors when JSON fails to parse, making it easier for users to fix their JSON.\n\n## How to Test\n1. Paste invalid JSON: `{invalid`\n2. Look for the red error indicator\n3. Hover over it to see the detailed error message\n\n## Evidence\n- [Screenshot of tooltip](link-to-image)\n\n## Performance Notes\nNo performance impact. Tooltip renders conditionally only on errors.\n```\n\n---\n\n## Questions?\n\n- Found a bug? Open an issue\n- Have an idea? Open an issue\n- Confused about something? Comment on the issue\n\nThank you for contributing to JSON Crack! 🎉\n"
  },
  {
    "path": "LICENSE.md",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2025 Aykut Saraç\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<!-- PROJECT LOGO -->\n<p align=\"center\">\n  <a href=\"https://github.com/AykutSarac/jsoncrack.com\">\n   <img src=\"./apps/www/public/assets/192.png\" height=\"50\" alt=\"Logo\">\n  </a>\n\n  <h1 align=\"center\">JSON Crack</h1>\n\n  <p align=\"center\">\n    The open-source JSON Editor.\n    <br />\n    <a href=\"https://jsoncrack.com\"><strong>Learn more »</strong></a>\n    <br />\n    <br />\n    <a href=\"https://todiagram.com\">ToDiagram</a>\n    ·\n    <a href=\"https://discord.gg/yVyTtCRueq\">Discord</a>\n    ·\n    <a href=\"https://jsoncrack.com\">Website</a>\n    ·\n    <a href=\"https://github.com/AykutSarac/jsoncrack.com/issues\">Issues</a>\n    ·\n    <a href=\"https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode\">VS Code</a>\n  </p>\n</p>\n\n<!-- ABOUT THE PROJECT -->\n\n## About the Project\n\n<img width=\"100%\" alt=\"booking-screen\" src=\"./apps/www/public/assets/editor.webp\">\n\n## Visualize JSON into interactive graphs\n\nJSON Crack is a tool for visualizing JSON data in a structured, interactive graphs, making it easier to explore, format, and validate JSON. It offers features like converting JSON to other formats (CSV, YAML), generating JSON Schema, executing queries, and exporting visualizations as images. Designed for both readability and usability.\n\n* **Visualizer**: Instantly convert JSON, YAML, CSV, and XML into interactive graphs or trees in dark or light mode.\n* **Convert**: Seamlessly transform data formats, like JSON to CSV or XML to JSON, for easy sharing.\n* **Format & Validate**: Beautify and validate JSON, YAML, and CSV for clear and accurate data.\n* **Code Generation**: Generate TypeScript interfaces, Golang structs, and JSON Schema.\n* **JSON Schema**: Create JSON Schema, mock data, and validate various data formats.\n* **Advanced Tools**: Decode JWT, randomize data, and run jq or JSON path queries.\n* **Export Image**: Download your visualization as PNG, JPEG, or SVG.\n* **Privacy**: All data processing is local; nothing is stored on our servers.\n\n## Recognition\n\n<a href=\"https://news.ycombinator.com/item?id=32626873\">\n  <img\n    style=\"width: 250px; height: 54px;\" width=\"250\" height=\"54\"\n    alt=\"Featured on Hacker News\"\n    src=\"https://hackernews-badge.vercel.app/api?id=32626873\"\n  />\n</a>\n\n<a href=\"https://producthunt.com/posts/JSON-Crack?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-jsoncrack\" target=\"_blank\"><img src=\"https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=332281&theme=light\" alt=\"JSON Crack | Product Hunt\" style=\"width: 250px; height: 54px;\" width=\"250\" height=\"54\" /></a>\n\n## Integrations\n\n- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode)\n- [npm Package (`jsoncrack-react`)](https://www.npmjs.com/package/jsoncrack-react)\n\n## Contributing\n\n- Found a bug or missing feature? Open an issue on [GitHub Issues](https://github.com/AykutSarac/jsoncrack.com/issues).\n- Want to contribute code or docs? Start with our [contribution guide](./CONTRIBUTING.md).\n\n## Sponsors & Support\n\nIf you find JSON Crack useful, you can support the project by using [ToDiagram](https://todiagram.com).\n\n## Stay Up-to-Date\n\nJSON Crack officially launched as v1.0 on the 17th of February 2022 and we've come a long way so far. Watch **releases** of this repository to be notified of future updates:\n\n<a href=\"https://github.com/AykutSarac/jsoncrack.com\"><img src=\"https://img.shields.io/github/stars/AykutSarac/jsoncrack.com\" alt=\"Star at GitHub\" /></a>\n\n<!-- GETTING STARTED -->\n\n## Getting Started\n\nTo get a local copy up and running, please follow these simple steps.\n\n### Prerequisites\n\nHere is what you need to be able to run JSON Crack.\n\n- Node.js (Version: >=24.x)\n- pnpm (Version: >=10)\n\n\n## Development\n\n### Setup\n\n1. Clone the repo into a public GitHub repository (or fork https://github.com/AykutSarac/jsoncrack.com/fork). If you plan to distribute the code, read the [`LICENSE`](/LICENSE.md) for additional details.\n\n   ```sh\n   git clone https://github.com/AykutSarac/jsoncrack.com.git\n   ```\n\n2. Go to the project folder\n\n   ```sh\n   cd jsoncrack.com\n   ```\n\n3. Install packages\n\n   ```sh\n   pnpm install\n   ```\n\n4. Run the web app\n\n   ```sh\n   pnpm dev:www\n\n   # Running on http://localhost:3000/\n   ```\n\n### Useful Commands\n\nFrom repository root:\n\n```sh\n# Web app\npnpm dev:www\npnpm build:www\n\n# VS Code extension\npnpm dev:vscode\npnpm build:vscode\npnpm lint:vscode\npnpm lint:fix:vscode\n\n# All workspaces\npnpm dev\npnpm build\npnpm lint\n```\n\n`pnpm build:www` is the production build command used in GitHub Actions deployment.\n\n### Debug VS Code Extension\n\n1. Open repository root in VS Code.\n2. Press `F5`.\n3. Select `Run VSCode Extension (apps/vscode)` when prompted.\n4. In the Extension Development Host window, open a `.json` file and run:\n   `JSON Crack: Enable JSON Crack visualization`.\n\n### Docker\n\n🐳 Docker assets are in `apps/www`.\nIf you want to run JSON Crack locally:\n\n```console\ncd apps/www\n\n# Build a Docker image with:\ndocker compose build\n\n# Run locally with `docker-compose`\ndocker compose up\n\n# Go to http://localhost:8888\n```\n\n## Configuration\n\nThe supported node limit can be changed by editing `NEXT_PUBLIC_NODE_LIMIT` in `apps/www/.env`.\n\n<!-- LICENSE -->\n\n## License\n\nSee [`LICENSE`](/LICENSE.md) for more information.\n"
  },
  {
    "path": "apps/vscode/.gitignore",
    "content": "node_modules/\nbuild/\n*.vsix"
  },
  {
    "path": "apps/vscode/.prettierignore",
    "content": ".github\n.next\nnode_modules/\nout\npublic\n*-lock.json\ntsconfig.json\nbuild\njsoncrack"
  },
  {
    "path": "apps/vscode/.prettierrc",
    "content": "{\n  \"trailingComma\": \"es5\",\n  \"singleQuote\": false,\n  \"semi\": true,\n  \"printWidth\": 100,\n  \"arrowParens\": \"avoid\",\n  \"importOrder\": [\n    \"^(react/(.*)$)|^(react$)\",\n    \"^(next/(.*)$)|^(next$)\",\n    \"^@mantine/core\",\n    \"^@mantine\",\n    \"styled\",\n    \"<THIRD_PARTY_MODULES>\",\n    \"^src/(.*)$\",\n    \"^[./]\"\n  ],\n  \"importOrderParserPlugins\": [\"typescript\", \"jsx\", \"decorators-legacy\"],\n  \"plugins\": [\"@trivago/prettier-plugin-sort-imports\"]\n}\n"
  },
  {
    "path": "apps/vscode/.vscodeignore",
    "content": ".vscode/**\n.vscode-test/**\n.github/**\n.turbo/**\nnode_modules/**\nsrc/**\next-src/**\n.gitignore\n.prettierignore\n.prettierrc\nindex.html\nvite.config.ts\nesbuild.config.mjs\neslint.config.mjs\n**/tsconfig.json\n**/*.map\n"
  },
  {
    "path": "apps/vscode/LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2025 Aykut Saraç\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "apps/vscode/README.md",
    "content": "  <img src=\"https://github.com/AykutSarac/jsoncrack-vscode/assets/47941171/23b26537-7c4a-4029-af78-456dea0d0b04\" width=\"300\" alt=\"JSON Crack\" />\n\n<hr />\n\n[JSON Crack](https://jsoncrack.com?utm_source=jsoncrack-vscode&utm_medium=readme)'s Official Visual Studio Code Extension that visualizes JSON data as an interactive diagram. The extension parses the open JSON file and displays its structure as a connected graph where nodes represent objects, arrays, and values.\n\n## How to use?\n\n1. Install the JSON Crack extension from the [VS Code marketplace](https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode).\n2. Open a JSON file.\n3. Click on the JSON Crack icon in the menubar at top right.\n\n<img width=\"600\" alt=\"image\" src=\"https://github.com/AykutSarac/jsoncrack-vscode/assets/47941171/06715ac1-2403-402f-b3fa-3d91e1c9196a\">\n\n## Privacy\n\nThe extension works **fully offline**. No data is sent to any server. All JSON parsing and visualization happens locally in your editor.\n\n## Development\n\nThis extension lives in `apps/vscode` inside the [jsoncrack.com](https://github.com/AykutSarac/jsoncrack.com) monorepo.\n\n**Prerequisites:** Node.js `>=20`, pnpm `>=10`\n\n**Stack:** Vite (webview) + esbuild (extension host) + React 19\n\n```sh\n# Install dependencies from repo root\npnpm install\n\n# Build the extension\ncd apps/vscode\npnpm run build\n```\n\n### Debugging\n\n1. Open the **monorepo root** in VS Code.\n2. Press **F5** to launch the \"Run VSCode Extension\" config — it builds and opens the Extension Development Host.\n3. After making changes, press `Cmd+R` (macOS) / `Ctrl+R` (Windows/Linux) in the host window to reload.\n\n### Scripts\n\n| Script | Description |\n|---|---|\n| `build` | Production build (minified, no sourcemaps) |\n| `build:dev` | Dev build (sourcemaps, no minification) |\n| `watch` | Watch extension host (`ext-src/`) for changes |\n| `watch:webview` | Watch webview (`src/`) for changes |\n| `dev` | Start Vite dev server (standalone webview in browser) |\n| `lint` | Run ESLint + Prettier check |\n| `clean` | Remove `build/` directory |\n"
  },
  {
    "path": "apps/vscode/esbuild.config.mjs",
    "content": "import * as esbuild from \"esbuild\";\n\nconst isWatch = process.argv.includes(\"--watch\");\nconst isProduction = process.argv.includes(\"--production\");\n\n/** @type {esbuild.BuildOptions} */\nconst config = {\n  entryPoints: [\"ext-src/extension.ts\"],\n  bundle: true,\n  outdir: \"build\",\n  external: [\"vscode\"],\n  format: \"cjs\",\n  platform: \"node\",\n  target: \"node20\",\n  sourcemap: !isProduction,\n  minify: isProduction,\n  logLevel: \"info\",\n};\n\nif (isWatch) {\n  const ctx = await esbuild.context(config);\n  await ctx.watch();\n  console.log(\"Watching for changes...\");\n} else {\n  await esbuild.build(config);\n}\n"
  },
  {
    "path": "apps/vscode/eslint.config.mjs",
    "content": "import eslint from \"@eslint/js\";\nimport { defineConfig, globalIgnores } from \"eslint/config\";\nimport eslintConfigPrettier from \"eslint-config-prettier/flat\";\nimport eslintPluginPrettier from \"eslint-plugin-prettier/recommended\";\nimport unusedImports from \"eslint-plugin-unused-imports\";\nimport globals from \"globals\";\nimport tseslint from \"typescript-eslint\";\n\nexport default defineConfig([\n  eslint.configs.recommended,\n  tseslint.configs.recommended,\n  eslintConfigPrettier,\n  eslintPluginPrettier,\n  {\n    languageOptions: {\n      globals: {\n        ...globals.browser,\n        ...globals.node,\n      },\n      parserOptions: {\n        ecmaVersion: \"latest\",\n        sourceType: \"module\",\n        tsconfigRootDir: import.meta.dirname,\n      },\n    },\n    plugins: {\n      \"unused-imports\": unusedImports,\n    },\n    rules: {\n      \"@typescript-eslint/consistent-type-imports\": \"error\",\n      \"unused-imports/no-unused-imports\": \"error\",\n      \"@typescript-eslint/no-explicit-any\": \"off\",\n      \"prettier/prettier\": \"error\",\n      \"space-in-parens\": \"error\",\n      \"no-empty\": \"error\",\n      \"no-multiple-empty-lines\": \"error\",\n      \"no-irregular-whitespace\": \"error\",\n      strict: [\"error\", \"never\"],\n      \"linebreak-style\": [\"error\", \"unix\"],\n      quotes: [\"error\", \"double\", { avoidEscape: true }],\n      semi: [\"error\", \"always\"],\n      \"prefer-const\": \"error\",\n      \"space-before-function-paren\": [\n        \"error\",\n        {\n          anonymous: \"always\",\n          named: \"never\",\n          asyncArrow: \"always\",\n        },\n      ],\n    },\n  },\n  globalIgnores([\"build/**\"]),\n]);\n"
  },
  {
    "path": "apps/vscode/ext-src/extension.ts",
    "content": "import * as path from \"path\";\nimport * as vscode from \"vscode\";\nimport { createWebviewPanel } from \"./webview\";\n\nfunction getPanelTitle(document?: vscode.TextDocument) {\n  if (!document) return \"JSON Crack\";\n\n  const fileName = path.basename(document.fileName);\n  return fileName || \"JSON Crack\";\n}\n\nexport function activate(context: vscode.ExtensionContext) {\n  context.subscriptions.push(\n    vscode.commands.registerCommand(\"jsoncrack-vscode.start\", () =>\n      createWebviewForActiveEditor(context)\n    ),\n    vscode.commands.registerCommand(\"jsoncrack-vscode.start.specific\", (content?: string) =>\n      createWebviewForContent(context, content)\n    ),\n    vscode.commands.registerCommand(\"jsoncrack-vscode.start.selected\", () =>\n      createWebviewForSelectedText(context)\n    )\n  );\n}\n\n// create webview for selected text\nasync function createWebviewForSelectedText(context: vscode.ExtensionContext) {\n  const editor = vscode.window.activeTextEditor;\n\n  if (editor && editor.selection.isEmpty) {\n    vscode.window.showInformationMessage(\"Please select some text first!\");\n    return;\n  }\n\n  const selectedText = editor?.document.getText(editor.selection);\n\n  // Create the webview panel and send the selected JSON content\n  const panel = createWebviewPanel(context, getPanelTitle(editor?.document));\n  panel.webview.postMessage({\n    json: selectedText,\n  });\n\n  const onReceiveMessage = panel.webview.onDidReceiveMessage(e => {\n    if (e === \"ready\") {\n      panel.webview.postMessage({\n        json: selectedText,\n      });\n    }\n  });\n\n  const onTextChange = vscode.workspace.onDidChangeTextDocument(changeEvent => {\n    if (changeEvent.document === editor?.document) {\n      panel.webview.postMessage({\n        json: changeEvent.document.getText(editor?.selection),\n      });\n    }\n  });\n\n  const disposer = () => {\n    onTextChange.dispose();\n    onReceiveMessage.dispose();\n  };\n\n  panel.onDidDispose(disposer, null, context.subscriptions);\n}\n\nasync function createWebviewForActiveEditor(context: vscode.ExtensionContext) {\n  const editor = vscode.window.activeTextEditor;\n  const panel = createWebviewPanel(context, getPanelTitle(editor?.document));\n\n  const onReceiveMessage = panel.webview.onDidReceiveMessage(e => {\n    if (e === \"ready\") {\n      panel.webview.postMessage({\n        json: editor?.document.getText(),\n      });\n    }\n  });\n\n  const onTextChange = vscode.workspace.onDidChangeTextDocument(changeEvent => {\n    if (changeEvent.document === editor?.document) {\n      panel.webview.postMessage({\n        json: changeEvent.document.getText(),\n      });\n    }\n  });\n\n  const disposer = () => {\n    onTextChange.dispose();\n    onReceiveMessage.dispose();\n  };\n\n  panel.onDidDispose(disposer, null, context.subscriptions);\n}\n\n/**\n * Renders a readonly diagram from a string\n * @param context ExtensionContext\n * @param content JSON content as a string\n */\nfunction createWebviewForContent(context?: vscode.ExtensionContext, content?: string): any {\n  if (context && content) {\n    const panel = createWebviewPanel(\n      context,\n      getPanelTitle(vscode.window.activeTextEditor?.document)\n    );\n    panel.webview.postMessage({\n      json: content,\n    });\n  }\n}\n\n// This method is called when your extension is deactivated\nexport function deactivate() {}\n"
  },
  {
    "path": "apps/vscode/ext-src/webview.ts",
    "content": "import * as path from \"path\";\nimport * as vscode from \"vscode\";\n\nexport function createWebviewPanel(context: vscode.ExtensionContext, title = \"JSON Crack\") {\n  const extPath = context.extensionPath;\n  const webviewDir = vscode.Uri.file(path.join(extPath, \"build\", \"webview\"));\n\n  const panel = vscode.window.createWebviewPanel(\n    \"liveHTMLPreviewer\",\n    title,\n    vscode.ViewColumn.Beside,\n    {\n      enableScripts: true,\n      retainContextWhenHidden: true,\n      localResourceRoots: [webviewDir, vscode.Uri.file(path.join(extPath, \"assets\"))],\n    }\n  );\n  panel.iconPath = vscode.Uri.file(path.join(extPath, \"assets\", \"jsoncrack.png\"));\n\n  const scriptUri = panel.webview.asWebviewUri(\n    vscode.Uri.file(path.join(extPath, \"build\", \"webview\", \"index.js\"))\n  );\n  const styleUri = panel.webview.asWebviewUri(\n    vscode.Uri.file(path.join(extPath, \"build\", \"webview\", \"index.css\"))\n  );\n\n  const nonce = getNonce();\n  const csp = [\n    `default-src 'self' ${panel.webview.cspSource} blob:`,\n    `connect-src ${panel.webview.cspSource} blob:`,\n    `script-src 'unsafe-eval' 'unsafe-inline' ${panel.webview.cspSource}`,\n    `style-src ${panel.webview.cspSource} 'unsafe-inline'`,\n    `worker-src ${panel.webview.cspSource} blob: data:`,\n  ].join(\"; \");\n\n  panel.webview.html = `<!DOCTYPE html>\n      <html lang=\"en\">\n      <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"Content-Security-Policy\" content=\"${csp}\">\n        <link href=\"${styleUri}\" rel=\"stylesheet\">\n      </head>\n      <body>\n        <noscript>You need to enable JavaScript to run this app.</noscript>\n        <div id=\"root\"></div>\n        <script nonce=\"${nonce}\" src=\"${scriptUri}\"></script>\n      </body>\n      </html>`;\n\n  return panel;\n}\n\nfunction getNonce() {\n  let text = \"\";\n  const possible = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n  for (let i = 0; i < 32; i++) {\n    text += possible.charAt(Math.floor(Math.random() * possible.length));\n  }\n  return text;\n}\n"
  },
  {
    "path": "apps/vscode/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/vscode/package.json",
    "content": "{\n  \"name\": \"vscode\",\n  \"version\": \"3.0.0\",\n  \"displayName\": \"JSON Crack\",\n  \"description\": \"Seamlessly visualize your JSON data instantly into graphs.\",\n  \"publisher\": \"AykutSarac\",\n  \"license\": \"MIT\",\n  \"author\": {\n    \"email\": \"aykutsarac0@gmail.com\",\n    \"name\": \"Aykut Saraç\"\n  },\n  \"homepage\": \"https://jsoncrack.com\",\n  \"icon\": \"assets/jsoncrack.png\",\n  \"galleryBanner\": {\n    \"color\": \"#202225\",\n    \"theme\": \"dark\"\n  },\n  \"categories\": [\n    \"Visualization\"\n  ],\n  \"keywords\": [\n    \"json\",\n    \"visualizer\",\n    \"jsoncrack\",\n    \"data\",\n    \"yaml\",\n    \"livepreview\"\n  ],\n  \"activationEvents\": [\n    \"workspaceContains:**/*.{json}\"\n  ],\n  \"main\": \"./build/extension.js\",\n  \"contributes\": {\n    \"commands\": [\n      {\n        \"command\": \"jsoncrack-vscode.start\",\n        \"title\": \"Enable JSON Crack visualization\",\n        \"category\": \"menubar\",\n        \"icon\": {\n          \"light\": \"./assets/icon-light.svg\",\n          \"dark\": \"./assets/icon-dark.svg\"\n        }\n      },\n      {\n        \"command\": \"jsoncrack-vscode.start.specific\",\n        \"title\": \"Enable JSON Crack visualization for specific file\",\n        \"category\": \"menubar\"\n      },\n      {\n        \"command\": \"jsoncrack-vscode.start.selected\",\n        \"title\": \"Open with JSON Crack\",\n        \"category\": \"Navigation\"\n      }\n    ],\n    \"menus\": {\n      \"editor/context\": [\n        {\n          \"command\": \"jsoncrack-vscode.start.selected\",\n          \"when\": \"editorHasSelection\",\n          \"group\": \"navigation\"\n        }\n      ],\n      \"commandPalette\": [\n        {\n          \"command\": \"jsoncrack-vscode.start\",\n          \"when\": \"never\"\n        }\n      ],\n      \"editor/title\": [\n        {\n          \"command\": \"jsoncrack-vscode.start\",\n          \"when\": \"resourceExtname == .json || editorLangId == json\",\n          \"group\": \"navigation\"\n        }\n      ]\n    }\n  },\n  \"scripts\": {\n    \"vscode:prepublish\": \"pnpm run build\",\n    \"dev\": \"vite\",\n    \"build\": \"vite build && node esbuild.config.mjs --production\",\n    \"build:dev\": \"vite build && node esbuild.config.mjs\",\n    \"watch\": \"node esbuild.config.mjs --watch\",\n    \"watch:webview\": \"vite build --watch\",\n    \"lint\": \"eslint src ext-src && prettier --check src ext-src\",\n    \"lint:fix\": \"eslint --fix src ext-src && prettier --write src ext-src\",\n    \"clean\": \"rm -rf build\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^10.0.1\",\n    \"@mantine/code-highlight\": \"^8.3.18\",\n    \"@mantine/core\": \"^8.3.18\",\n    \"@mantine/hooks\": \"^8.3.18\",\n    \"@trivago/prettier-plugin-sort-imports\": \"^6.0.2\",\n    \"@types/node\": \"^22.19.15\",\n    \"@types/react\": \"19.2.11\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"@types/vscode\": \"^1.110.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.57.1\",\n    \"@typescript-eslint/parser\": \"^8.57.1\",\n    \"@vitejs/plugin-react\": \"^4.7.0\",\n    \"esbuild\": \"^0.25.12\",\n    \"eslint\": \"^10.0.3\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"eslint-plugin-unused-imports\": \"^4.4.1\",\n    \"globals\": \"^17.4.0\",\n    \"prettier\": \"^3.8.1\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.57.1\",\n    \"vite\": \"^6.4.1\"\n  },\n  \"dependencies\": {\n    \"jsoncrack-react\": \"workspace:*\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"shiki\": \"^4.0.2\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/AykutSarac/jsoncrack.com\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/AykutSarac/jsoncrack.com/issues\"\n  },\n  \"engines\": {\n    \"vscode\": \"^1.86.0\"\n  },\n  \"packageManager\": \"pnpm@10.20.0\"\n}\n"
  },
  {
    "path": "apps/vscode/src/App.tsx",
    "content": "import { useCallback, useEffect, useState } from \"react\";\nimport { Anchor, Box, MantineProvider, Text } from \"@mantine/core\";\nimport { CodeHighlightAdapterProvider, createShikiAdapter } from \"@mantine/code-highlight\";\nimport type { NodeData } from \"jsoncrack-react\";\nimport { JSONCrack } from \"jsoncrack-react\";\nimport { NodeModal } from \"./components/NodeModal\";\n\nasync function loadShiki() {\n  const { createHighlighter } = await import(\"shiki\");\n  return createHighlighter({ langs: [\"json\"], themes: [] });\n}\n\nconst shikiAdapter = createShikiAdapter(loadShiki);\n\nfunction getTheme() {\n  const theme = document.body.getAttribute(\"data-vscode-theme-kind\");\n  if (theme?.includes(\"light\")) return \"light\" as const;\n  return \"dark\";\n}\n\nconst App: React.FC = () => {\n  const [json, setJson] = useState(\"{}\");\n  const [selectedNode, setSelectedNode] = useState<NodeData | null>(null);\n  const theme = getTheme();\n\n  useEffect(() => {\n    const vscode = window?.acquireVsCodeApi?.();\n    vscode?.postMessage(\"ready\");\n\n    const onMessage = (event: MessageEvent<{ json?: string }>) => {\n      const jsonData = event.data?.json;\n      if (typeof jsonData === \"string\") {\n        setJson(jsonData);\n      }\n    };\n\n    window.addEventListener(\"message\", onMessage);\n\n    return () => {\n      window.removeEventListener(\"message\", onMessage);\n    };\n  }, []);\n\n  const handleNodeClick = useCallback((node: NodeData) => {\n    setSelectedNode(node);\n  }, []);\n\n  const closeNodeModal = useCallback(() => {\n    setSelectedNode(null);\n  }, []);\n\n  return (\n    <MantineProvider forceColorScheme={theme}>\n      <CodeHighlightAdapterProvider adapter={shikiAdapter}>\n        <Box h=\"100vh\" w=\"100vw\">\n          <JSONCrack json={json} theme={theme} showControls={false} onNodeClick={handleNodeClick} />\n          {selectedNode && (\n            <NodeModal opened={!!selectedNode} onClose={closeNodeModal} nodeData={selectedNode} />\n          )}\n          <Anchor\n            pos=\"fixed\"\n            bottom={0}\n            left={0}\n            href=\"https://jsoncrack.com/editor?utm_source=vscode&utm_campaign=attribute\"\n            target=\"_blank\"\n          >\n            <Box px=\"12\" py=\"4\" bg=\"dark\">\n              <Text fz=\"sm\" c=\"white\">\n                Powered by JSON Crack\n              </Text>\n            </Box>\n          </Anchor>\n        </Box>\n      </CodeHighlightAdapterProvider>\n    </MantineProvider>\n  );\n};\n\nexport default App;\n\ndeclare global {\n  interface Window {\n    acquireVsCodeApi?: () => any;\n  }\n}\n"
  },
  {
    "path": "apps/vscode/src/components/NodeModal.tsx",
    "content": "import type { ModalProps } from \"@mantine/core\";\nimport { Modal, Stack, Text, ScrollArea } from \"@mantine/core\";\nimport { CodeHighlight } from \"@mantine/code-highlight\";\nimport type { NodeData } from \"jsoncrack-react\";\n\ninterface NodeModalProps extends ModalProps {\n  nodeData: NodeData | null;\n}\n\nconst normalizeNodeData = (nodeRows: NodeData[\"text\"]) => {\n  if (!nodeRows || nodeRows.length === 0) return \"{}\";\n  if (nodeRows.length === 1 && !nodeRows[0].key) return `${nodeRows[0].value}`;\n\n  const obj: Record<string, unknown> = {};\n  nodeRows.forEach(row => {\n    if (row.type !== \"array\" && row.type !== \"object\" && row.key) {\n      obj[row.key] = row.value;\n    }\n  });\n\n  return JSON.stringify(obj, null, 2);\n};\n\nconst jsonPathToString = (path?: NodeData[\"path\"]) => {\n  if (!path || path.length === 0) return \"$\";\n  const segments = path.map(seg => (typeof seg === \"number\" ? seg : `\"${seg}\"`));\n  return `$[${segments.join(\"][\")}]`;\n};\n\nexport const NodeModal = ({ opened, onClose, nodeData }: NodeModalProps) => {\n  const nodeContent = normalizeNodeData(nodeData?.text ?? []);\n  const jsonPath = jsonPathToString(nodeData?.path);\n\n  return (\n    <Modal title=\"Node Content\" size=\"auto\" opened={opened} onClose={onClose} centered>\n      <Stack py=\"sm\" gap=\"sm\">\n        <Stack gap=\"xs\">\n          <Text fz=\"xs\" fw={500}>\n            Content\n          </Text>\n          <ScrollArea.Autosize mah={250} maw={600}>\n            <CodeHighlight code={nodeContent} miw={350} maw={600} language=\"json\" withCopyButton />\n          </ScrollArea.Autosize>\n        </Stack>\n        <Text fz=\"xs\" fw={500}>\n          JSON Path\n        </Text>\n        <ScrollArea.Autosize maw={600}>\n          <CodeHighlight\n            code={jsonPath}\n            miw={350}\n            mah={250}\n            language=\"json\"\n            copyLabel=\"Copy to clipboard\"\n            copiedLabel=\"Copied to clipboard\"\n            withCopyButton\n          />\n        </ScrollArea.Autosize>\n      </Stack>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "apps/vscode/src/env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "apps/vscode/src/global.css",
    "content": "html,\nbody,\n#root {\n  margin: 0;\n  padding: 0;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n}\n\na {\n  color: inherit;\n  text-decoration: none;\n}\n"
  },
  {
    "path": "apps/vscode/src/index.tsx",
    "content": "import \"@mantine/core/styles.css\";\nimport \"@mantine/code-highlight/styles.css\";\nimport \"jsoncrack-react/style.css\";\nimport { createRoot } from \"react-dom/client\";\nimport App from \"./App\";\nimport \"./global.css\";\n\nconst container = document.getElementById(\"root\") as HTMLElement;\nconst root = createRoot(container);\nroot.render(<App />);\n"
  },
  {
    "path": "apps/vscode/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"noImplicitAny\": false\n  },\n  \"include\": [\"src\", \"ext-src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/vscode/vite.config.ts",
    "content": "import react from \"@vitejs/plugin-react\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport default defineConfig({\n  plugins: [react()],\n  build: {\n    outDir: \"build/webview\",\n    emptyOutDir: true,\n    rollupOptions: {\n      input: \"src/index.tsx\",\n      output: {\n        entryFileNames: \"index.js\",\n        assetFileNames: \"index[extname]\",\n        inlineDynamicImports: true,\n      },\n    },\n  },\n  resolve: {\n    alias: {\n      react: path.resolve(__dirname, \"node_modules/react\"),\n      \"react-dom\": path.resolve(__dirname, \"node_modules/react-dom\"),\n    },\n  },\n});\n"
  },
  {
    "path": "apps/www/.dockerignore",
    "content": "Dockerfile\n.dockerignore\nnode_modules\nnpm-debug.log\nREADME.md\n.next\n.git"
  },
  {
    "path": "apps/www/.gitignore",
    "content": "# App dependencies\nnode_modules/\n\n# Next.js\n.next/\nout/\n\n# Turborepo task cache\n.turbo/\n\n# Test/build artifacts\ncoverage/\nbuild/\n*.tsbuildinfo\n\n# Local environment files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Deployment\n.vercel\n\n# Workspace migration artifact (root lockfile is authoritative)\npnpm-lock.yaml\n\n# PWA workers\npublic/workbox-*.js\npublic/sw.js\npublic/fallback-*.js\n"
  },
  {
    "path": "apps/www/.prettierignore",
    "content": ".github\n.next\nnode_modules/\nout\npublic\n*-lock.json\ntsconfig.json"
  },
  {
    "path": "apps/www/.prettierrc",
    "content": "{\n  \"trailingComma\": \"es5\",\n  \"singleQuote\": false,\n  \"semi\": true,\n  \"printWidth\": 100,\n  \"arrowParens\": \"avoid\",\n  \"importOrder\": [\n    \"^(react/(.*)$)|^(react$)\",\n    \"^(next/(.*)$)|^(next$)\",\n    \"^@mantine/core\",\n    \"^@mantine\",\n    \"styled\",\n    \"<THIRD_PARTY_MODULES>\",\n    \"^src/(.*)$\",\n    \"^[./]\"\n  ],\n  \"importOrderParserPlugins\": [\"typescript\", \"jsx\", \"decorators-legacy\"],\n  \"plugins\": [\"@trivago/prettier-plugin-sort-imports\"]\n}\n"
  },
  {
    "path": "apps/www/Dockerfile",
    "content": "FROM node:24.10.0-alpine AS base\nRUN npm install -g pnpm@10.10.0\n\n# Stage 1: Install dependencies\nFROM base AS deps\nWORKDIR /app\nCOPY package.json pnpm-lock.yaml ./\nRUN corepack enable pnpm && pnpm install --frozen-lockfile\n\n# Stage 2: Build the application\nFROM base AS builder\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY . .\nRUN corepack enable pnpm && pnpm run build\n\n# Stage 3: Production image\nFROM nginxinc/nginx-unprivileged:stable AS production\nWORKDIR /app\nCOPY --from=builder /app/out /app\nCOPY ./nginx.conf /etc/nginx/conf.d/default.conf\n\nEXPOSE 8080\n"
  },
  {
    "path": "apps/www/LICENSE.md",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2025 Aykut Saraç\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "apps/www/docker-compose.yml",
    "content": "services:\n  jsoncrack:\n    image: jsoncrack\n    container_name: jsoncrack\n    build: \n      context: .\n      dockerfile: Dockerfile\n    ports:\n      - \"8888:8080\"\n    environment:\n      - NODE_ENV=production"
  },
  {
    "path": "apps/www/eslint.config.mjs",
    "content": "import eslint from \"@eslint/js\";\nimport nextPlugin from \"@next/eslint-plugin-next\";\nimport eslintConfigPrettier from \"eslint-config-prettier/flat\";\nimport eslintPluginPrettier from \"eslint-plugin-prettier/recommended\";\nimport unusedImports from \"eslint-plugin-unused-imports\";\nimport { defineConfig, globalIgnores } from \"eslint/config\";\nimport tseslint from \"typescript-eslint\";\n\nexport default defineConfig([\n  globalIgnores([\"src/enums\", \"**/next.config.js\", \"src/lib/utils/json2go.js\"]),\n  eslint.configs.recommended,\n  tseslint.configs.recommended,\n  {\n    plugins: {\n      \"@next/next\": nextPlugin,\n    },\n    rules: {\n      ...nextPlugin.configs.recommended.rules,\n      ...nextPlugin.configs[\"core-web-vitals\"].rules,\n      \"@next/next/no-img-element\": \"off\",\n    },\n  },\n  eslintConfigPrettier,\n  eslintPluginPrettier,\n  {\n    languageOptions: {\n      parserOptions: {\n        tsconfigRootDir: import.meta.dirname,\n      },\n    },\n    plugins: {\n      \"unused-imports\": unusedImports,\n    },\n    rules: {\n      \"@typescript-eslint/consistent-type-imports\": \"error\",\n      \"unused-imports/no-unused-imports\": \"error\",\n      \"@typescript-eslint/no-explicit-any\": \"off\",\n      \"prettier/prettier\": \"error\",\n      \"space-in-parens\": \"error\",\n      \"no-empty\": \"error\",\n      \"no-multiple-empty-lines\": \"error\",\n      \"no-irregular-whitespace\": \"error\",\n      strict: [\"error\", \"never\"],\n      \"linebreak-style\": [\"error\", \"unix\"],\n      quotes: [\"error\", \"double\", { avoidEscape: true }],\n      semi: [\"error\", \"always\"],\n      \"prefer-const\": \"error\",\n      \"space-before-function-paren\": [\n        \"error\",\n        {\n          anonymous: \"always\",\n          named: \"never\",\n          asyncArrow: \"always\",\n        },\n      ],\n    },\n  },\n]);\n"
  },
  {
    "path": "apps/www/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/dev/types/routes.d.ts\";\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.\n"
  },
  {
    "path": "apps/www/next-sitemap.config.js",
    "content": "/** @type {import('next-sitemap').IConfig} */\nmodule.exports = {\n  siteUrl: \"https://jsoncrack.com\",\n  exclude: [\"/widget\"],\n  autoLastmod: false,\n  changefreq: \"never\",\n};\n"
  },
  {
    "path": "apps/www/next.config.js",
    "content": "const withBundleAnalyzer = require(\"@next/bundle-analyzer\")({\n  enabled: process.env.ANALYZE === \"true\",\n});\n\n/**\n * @type {import('next').NextConfig}\n */\nconst config = {\n  output: \"export\",\n  transpilePackages: [\"jsoncrack\"],\n  reactStrictMode: false,\n  productionBrowserSourceMaps: true,\n  compiler: {\n    styledComponents: true,\n  },\n  turbopack: {\n    resolveAlias: {\n      fs: {\n        browser: \"./shims/empty.ts\",\n      },\n    },\n  },\n  webpack: (config, { isServer }) => {\n    config.resolve.fallback = { fs: false };\n    config.output.webassemblyModuleFilename = \"static/wasm/[modulehash].wasm\";\n    config.experiments = { asyncWebAssembly: true, layers: true };\n\n    if (!isServer) {\n      config.output.environment = { ...config.output.environment, asyncFunction: true };\n    }\n\n    return config;\n  },\n};\n\nconst configExport = () => {\n  if (process.env.ANALYZE === \"true\") return withBundleAnalyzer(config);\n  return config;\n};\n\nmodule.exports = configExport();\n"
  },
  {
    "path": "apps/www/nginx.conf",
    "content": "server {\n    listen 8080;\n    root  /app;\n    include /etc/nginx/mime.types;\n\n    location /editor {\n        try_files $uri /editor.html;\n    }\n    \n    location /widget {\n        try_files $uri /widget.html;\n    }\n\n    location /docs {\n        try_files $uri /docs.html;\n    }\n}\n"
  },
  {
    "path": "apps/www/package.json",
    "content": "{\n  \"name\": \"www\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"license\": \"Apache-2.0\",\n  \"scripts\": {\n    \"dev\": \"next dev --webpack\",\n    \"build\": \"next build --webpack\",\n    \"postbuild\": \"next-sitemap --config next-sitemap.config.js\",\n    \"start\": \"next start\",\n    \"lint\": \"tsc --project tsconfig.json && eslint src && prettier --check src\",\n    \"lint:fix\": \"eslint --fix src && prettier --write src\",\n    \"analyze\": \"ANALYZE=true npm run build\"\n  },\n  \"dependencies\": {\n    \"@mantine/code-highlight\": \"^8.3.18\",\n    \"@mantine/core\": \"^8.3.18\",\n    \"@mantine/dropzone\": \"^8.3.18\",\n    \"@mantine/hooks\": \"^8.3.18\",\n    \"@monaco-editor/react\": \"^4.7.0\",\n    \"allotment\": \"^1.20.5\",\n    \"fast-xml-parser\": \"5.5.6\",\n    \"gofmt.js\": \"0.0.2\",\n    \"html-to-image\": \"1.11.13\",\n    \"jq-web\": \"0.6.2\",\n    \"js-yaml\": \"4.1.1\",\n    \"json-2-csv\": \"5.5.10\",\n    \"json-schema-faker\": \"0.6.0\",\n    \"json_typegen_wasm\": \"0.7.0\",\n    \"jsonc-parser\": \"3.3.1\",\n    \"jsoncrack-react\": \"workspace:*\",\n    \"jsonpath-plus\": \"10.4.0\",\n    \"lodash.debounce\": \"^4.0.8\",\n    \"next\": \"16.1.7\",\n    \"next-seo\": \"^7.2.0\",\n    \"next-sitemap\": \"^4.2.3\",\n    \"nextjs-google-analytics\": \"^2.3.7\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"react-hot-toast\": \"^2.6.0\",\n    \"react-icons\": \"^5.6.0\",\n    \"react-json-tree\": \"^0.20.0\",\n    \"react-linkify-it\": \"^2.0.0\",\n    \"react-zoomable-ui\": \"^0.11.0\",\n    \"reaflow\": \"5.4.1\",\n    \"shiki\": \"^4.0.2\",\n    \"styled-components\": \"^6.3.11\",\n    \"use-long-press\": \"^3.3.0\",\n    \"zustand\": \"^5.0.12\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^10.0.1\",\n    \"@next/bundle-analyzer\": \"16.1.7\",\n    \"@next/eslint-plugin-next\": \"16.1.7\",\n    \"@trivago/prettier-plugin-sort-imports\": \"^6.0.2\",\n    \"@types/js-yaml\": \"^4.0.9\",\n    \"@types/node\": \"^25.5.0\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"eslint\": \"^10.0.3\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"eslint-plugin-unused-imports\": \"^4.4.1\",\n    \"prettier\": \"^3.8.1\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"5.9.3\",\n    \"typescript-eslint\": \"^8.57.1\"\n  },\n  \"packageManager\": \"pnpm@10.20.0\"\n}\n"
  },
  {
    "path": "apps/www/public/.nojekyll",
    "content": ""
  },
  {
    "path": "apps/www/public/CNAME",
    "content": "jsoncrack.com"
  },
  {
    "path": "apps/www/public/manifest.json",
    "content": "{\n  \"name\": \"JSON Crack\",\n  \"short_name\": \"JSON Crack\",\n  \"description\": \"JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.\",\n  \"theme_color\": \"#36393e\",\n  \"background_color\": \"#36393e\",\n  \"display\": \"standalone\",\n  \"orientation\": \"landscape\",\n  \"scope\": \"/editor\",\n  \"start_url\": \"/editor\",\n  \"icons\": [\n    {\n      \"src\": \"assets/192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n\n    {\n      \"src\": \"assets/512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ]\n}\n"
  },
  {
    "path": "apps/www/public/robots.txt",
    "content": "User-agent: *\n\nAllow: /\n\nSitemap: https://jsoncrack.com/sitemap.xml\n"
  },
  {
    "path": "apps/www/public/sitemap-0.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:news=\"http://www.google.com/schemas/sitemap-news/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" xmlns:mobile=\"http://www.google.com/schemas/sitemap-mobile/1.0\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" xmlns:video=\"http://www.google.com/schemas/sitemap-video/1.1\">\n<url><loc>https://jsoncrack.com</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/csv-to-json</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/csv-to-xml</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/csv-to-yaml</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/json-to-csv</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/json-to-xml</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/json-to-yaml</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/xml-to-csv</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/xml-to-json</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/xml-to-yaml</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/yaml-to-csv</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/yaml-to-json</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/converter/yaml-to-xml</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/docs</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/editor</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/legal/privacy</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/legal/terms</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/tools/json-schema</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/csv-to-go</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/csv-to-kotlin</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/csv-to-rust</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/csv-to-typescript</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/json-to-go</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/json-to-kotlin</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/json-to-rust</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/json-to-typescript</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/xml-to-go</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/xml-to-kotlin</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/xml-to-rust</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/xml-to-typescript</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/yaml-to-go</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/yaml-to-kotlin</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/yaml-to-rust</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n<url><loc>https://jsoncrack.com/type/yaml-to-typescript</loc><changefreq>never</changefreq><priority>0.7</priority></url>\n</urlset>"
  },
  {
    "path": "apps/www/public/sitemap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n<sitemap><loc>https://jsoncrack.com/sitemap-0.xml</loc></sitemap>\n</sitemapindex>"
  },
  {
    "path": "apps/www/shims/empty.ts",
    "content": "export {};\n"
  },
  {
    "path": "apps/www/src/constants/globalStyle.ts",
    "content": "import { createGlobalStyle } from \"styled-components\";\n\nconst GlobalStyle = createGlobalStyle`\n  html, body {\n    background: #ffffff;\n    overscroll-behavior: none;\n    -webkit-font-smoothing: subpixel-antialiased !important;\n  }\n\n  *,\n  *::before,\n  *::after {\n      box-sizing: border-box;\n      margin: 0;\n      padding: 0;\n      scroll-behavior: smooth !important;\n      -webkit-tap-highlight-color: transparent;\n      -webkit-font-smoothing: never;\n  }\n\n  .hide {\n    display: none;\n  }\n\n  svg {\n    vertical-align: text-top;\n  }\n\n  a {\n    color: unset;\n    text-decoration: none;\n  }\n\n  button {\n    border: none;\n    outline: none;\n    background: transparent;\n    width: fit-content;\n    margin: 0;\n    padding: 0;\n    cursor: pointer;\n  }\n`;\n\nexport default GlobalStyle;\n"
  },
  {
    "path": "apps/www/src/constants/graph.ts",
    "content": "export const NODE_DIMENSIONS = {\n  ROW_HEIGHT: 30, // Regular row height\n  PARENT_HEIGHT: 36, // Height for parent nodes\n} as const;\n\nexport const SUPPORTED_LIMIT = +(process.env.NEXT_PUBLIC_NODE_LIMIT as string);\n"
  },
  {
    "path": "apps/www/src/constants/seo.ts",
    "content": "import type { DefaultSeoProps } from \"next-seo/pages\";\n\nexport const SEO: DefaultSeoProps = {\n  title: \"JSON Crack | Online JSON Viewer - Transform your data into interactive graphs\",\n  description:\n    \"JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.\",\n  themeColor: \"#36393E\",\n  openGraph: {\n    type: \"website\",\n    images: [\n      {\n        url: \"https://jsoncrack.com/assets/jsoncrack.png\",\n        width: 1200,\n        height: 627,\n      },\n    ],\n  },\n  twitter: {\n    handle: \"@jsoncrack\",\n    cardType: \"summary_large_image\",\n  },\n  additionalLinkTags: [\n    {\n      rel: \"manifest\",\n      href: \"/manifest.json\",\n    },\n    {\n      rel: \"icon\",\n      href: \"/favicon.ico\",\n      sizes: \"48x48\",\n    },\n  ],\n};\n"
  },
  {
    "path": "apps/www/src/constants/theme.ts",
    "content": "const fixedColors = {\n  CRIMSON: \"#DC143C\",\n  BLURPLE: \"#5865F2\",\n  PURPLE: \"#9036AF\",\n  FULL_WHITE: \"#FFFFFF\",\n  BLACK: \"#202225\",\n  BLACK_DARK: \"#2C2F33\",\n  BLACK_LIGHT: \"#2F3136\",\n  BLACK_PRIMARY: \"#36393f\",\n  DARK_SALMON: \"#E9967A\",\n  DANGER: \"hsl(359,calc(var(--saturation-factor, 1)*66.7%),54.1%)\",\n  LIGHTGREEN: \"#90EE90\",\n  SEAGREEN: \"#11883B\",\n  ORANGE: \"#FAA81A\",\n  SILVER: \"#B9BBBE\",\n  PRIMARY: \"#4D4D4D\",\n  TEXT_DANGER: \"#db662e\",\n};\n\nconst nodeColors = {\n  dark: {\n    NODE_COLORS: {\n      TEXT: \"#DCE5E7\",\n      NODE_KEY: \"#59b8ff\",\n      NODE_VALUE: \"#DCE5E7\",\n      INTEGER: \"#e8c479\",\n      NULL: \"#939598\",\n      BOOL: {\n        FALSE: \"#F85C50\",\n        TRUE: \"#00DC7D\",\n      },\n      PARENT_ARR: \"#FC9A40\",\n      PARENT_OBJ: \"#59b8ff\",\n      CHILD_COUNT: \"white\",\n      DIVIDER: \"#383838\",\n    },\n  },\n  light: {\n    NODE_COLORS: {\n      TEXT: \"#000\",\n      NODE_KEY: \"#761CEA\",\n      NODE_VALUE: \"#535353\",\n      INTEGER: \"#FD0079\",\n      NULL: \"#afafaf\",\n      BOOL: {\n        FALSE: \"#FF0000\",\n        TRUE: \"#748700\",\n      },\n      PARENT_ARR: \"#FF6B00\",\n      PARENT_OBJ: \"#761CEA\",\n      CHILD_COUNT: \"#535353\",\n      DIVIDER: \"#e6e6e6\",\n    },\n  },\n};\n\nexport const darkTheme = {\n  ...fixedColors,\n  ...nodeColors.dark,\n  BLACK_SECONDARY: \"#23272A\",\n  SILVER_DARK: \"#4D4D4D\",\n  NODE_KEY: \"#FAA81A\",\n  OBJECT_KEY: \"#59b8ff\",\n  SIDEBAR_ICONS: \"#8B8E90\",\n\n  INTERACTIVE_NORMAL: \"#b9bbbe\",\n  INTERACTIVE_HOVER: \"#dcddde\",\n  INTERACTIVE_ACTIVE: \"#fff\",\n  BACKGROUND_NODE: \"#2B2C3E\",\n  BACKGROUND_TERTIARY: \"#202225\",\n  BACKGROUND_SECONDARY: \"#2f3136\",\n  TOOLBAR_BG: \"#262626\",\n  BACKGROUND_PRIMARY: \"#36393f\",\n  BACKGROUND_MODIFIER_ACCENT: \"rgba(79,84,92,0.48)\",\n  MODAL_BACKGROUND: \"#36393E\",\n  TEXT_NORMAL: \"#dcddde\",\n  TEXT_POSITIVE: \"hsl(139,calc(var(--saturation-factor, 1)*51.6%),52.2%)\",\n  GRID_BG_COLOR: \"#141414\",\n  GRID_COLOR_PRIMARY: \"#1c1b1b\",\n  GRID_COLOR_SECONDARY: \"#191919\",\n};\n\nexport const lightTheme = {\n  ...fixedColors,\n  ...nodeColors.light,\n  BLACK_SECONDARY: \"#F2F2F2\",\n  SILVER_DARK: \"#CCCCCC\",\n  NODE_KEY: \"#DC3790\",\n  OBJECT_KEY: \"#0260E8\",\n  SIDEBAR_ICONS: \"#6D6E70\",\n\n  INTERACTIVE_NORMAL: \"#4f5660\",\n  INTERACTIVE_HOVER: \"#2e3338\",\n  INTERACTIVE_ACTIVE: \"#060607\",\n  BACKGROUND_NODE: \"#F6F8FA\",\n  BACKGROUND_TERTIARY: \"#e3e5e8\",\n  BACKGROUND_SECONDARY: \"#f2f3f5\",\n  TOOLBAR_BG: \"#ECECEC\",\n  BACKGROUND_PRIMARY: \"#FFFFFF\",\n  BACKGROUND_MODIFIER_ACCENT: \"rgba(106,116,128,0.24)\",\n  MODAL_BACKGROUND: \"#FFFFFF\",\n  TEXT_NORMAL: \"#2e3338\",\n  TEXT_POSITIVE: \"#008736\",\n  GRID_BG_COLOR: \"#f7f7f7\",\n  GRID_COLOR_PRIMARY: \"#ebe8e8\",\n  GRID_COLOR_SECONDARY: \"#f2eeee\",\n};\n\nconst themeDs = {\n  ...lightTheme,\n  ...darkTheme,\n};\n\nexport default themeDs;\n"
  },
  {
    "path": "apps/www/src/data/example.json",
    "content": "{\n  \"fruits\": [\n    {\n      \"name\": \"Apple\",\n      \"color\": \"#FF0000\",\n      \"details\": {\n        \"type\": \"Pome\",\n        \"season\": \"Fall\"\n      },\n      \"nutrients\": {\n        \"calories\": 52,\n        \"fiber\": \"2.4g\",\n        \"vitaminC\": \"4.6mg\"\n      }\n    },\n    {\n      \"name\": \"Banana\",\n      \"color\": \"#FFFF00\",\n      \"details\": {\n        \"type\": \"Berry\",\n        \"season\": \"Year-round\"\n      },\n      \"nutrients\": {\n        \"calories\": 89,\n        \"fiber\": \"2.6g\",\n        \"potassium\": \"358mg\"\n      }\n    },\n    {\n      \"name\": \"Orange\",\n      \"color\": \"#FFA500\",\n      \"details\": {\n        \"type\": \"Citrus\",\n        \"season\": \"Winter\"\n      },\n      \"nutrients\": {\n        \"calories\": 47,\n        \"fiber\": \"2.4g\",\n        \"vitaminC\": \"53.2mg\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "apps/www/src/data/faq.json",
    "content": "[\n  {\n    \"title\": \"What is JSON Crack and what does it do?\",\n    \"content\": \"JSON Crack is an online JSON viewer tool designed to visualize and analyze various data formats, including JSON, YAML, CSV, XML and more. It transforms complex data into intuitive graphs and tree views, making it ideal for developers, data analysts, and anyone working with structured data.\"\n  },\n  {\n    \"title\": \"How is it different than traditional JSON viewers?\",\n    \"content\": \"While traditional JSON Viewers and JSON formatters only allow you to work with raw data on text editors, JSON Crack offers a unique visual representation of your data, making it easier to understand and analyze complex data structures. It provides a tree view and graph view to help you visualize your data in different ways.\"\n  },\n  {\n    \"title\": \"Is JSON Crack free?\",\n    \"content\": \"Yes, JSON Crack is a free-forever open source online tool. For advanced features you may use ToDiagram.com\"\n  },\n  {\n    \"title\": \"Is my data secure?\",\n    \"content\": \"Yes. When you paste or import your data into the editor, it's processed only on your browser to create the visualization without going into our servers.\"\n  },\n  {\n    \"title\": \"Can I convert JSON to other formats using JSON Crack?\",\n    \"content\": \"Yes, JSON Crack offers robust data conversion capabilities. You can easily convert JSON to YAML, XML to JSON, CSV to JSON and other popular formats.\"\n  },\n  {\n    \"title\": \"What kind of data formats are supported?\",\n    \"content\": \"A wide range of data formats are supported including JSON, YAML, XML, and CSV.\"\n  },\n  {\n    \"title\": \"What size of data can I visualize?\",\n    \"content\": \"It supports approximately 300 KB. It might vary depending on the complexity of the data and your hardware.\"\n  },\n  {\n    \"title\": \"Can I export the generated graphs?\",\n    \"content\": \"Yes, you can export the generated graphs as PNG, JPEG, or SVG files.\"\n  },\n  {\n    \"title\": \"How to use VS Code extension?\",\n    \"content\": \"You can use the VS Code extension to visualize JSON data directly in your editor. Install the extension from the VS Code marketplace and follow the instructions at extension's page.\"\n  },\n  {\n    \"title\": \"I've previously subscribed to the premium plan, where did it go?\",\n    \"content\": \"We have moved the premium features to ToDiagram.com. You can use the same credentials to access the premium features or manage your subscription.\"\n  }\n]\n"
  },
  {
    "path": "apps/www/src/data/privacy.json",
    "content": "{\n  \"Introduction\": [\n    \"Welcome to JSON Crack.\",\n    \"JSON Crack (“us”, “we”, or “our”) operates https://jsoncrack.com (hereinafter referred to as “Service”).\",\n    \"Our Privacy Policy governs your visit to https://jsoncrack.com, explaining how we collect, safeguard, and disclose information resulting from your use of our Service.\",\n    \"By using the Service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, the terms used have the same meanings as in our Terms and Conditions.\",\n    \"Our Terms and Conditions (“Terms”) govern all use of our Service and together with the Privacy Policy constitute your agreement with us (“agreement”).\"\n  ],\n  \"Definitions\": [\n    \"SERVICE means the https://jsoncrack.com website.\",\n    \"PERSONAL DATA means data about a living individual who can be identified from those data.\",\n    \"USAGE DATA is data collected automatically, generated by the use of Service or from Service infrastructure itself (e.g., the duration of a page visit).\",\n    \"COOKIES are small files stored on your device (computer or mobile device).\"\n  ],\n  \"Information Collection and Use\": [\n    \"We collect limited information for the purpose of improving our Service. We do not collect or store any Personal Data beyond what is necessary for analytics and security.\"\n  ],\n  \"Data Collection\": [\n    \"Usage Data\",\n    \"• We may collect information that your browser sends whenever you visit our Service (“Usage Data”).\",\n    \"• This may include your computer's IP address, browser type, browser version, pages visited, time and date of your visit, time spent on pages, and other diagnostic data.\",\n    \"Cookies\",\n    \"• We use cookies and similar tracking technologies to monitor activity on our Service.\",\n    \"• Cookies are files with a small amount of data that may include an anonymous unique identifier.\"\n  ],\n  \"Use of Data\": [\n    \"JSON Crack uses collected data to:\",\n    \"• Provide and maintain the Service;\",\n    \"• Improve and analyze Service performance.\"\n  ],\n  \"Security of Data\": [\n    \"The data you paste into the editor for transformation into visualizations is not stored or processed on our servers, ensuring that your information remains private.\",\n    \"Usage Data may be shared with third-party analytics services as outlined below.\"\n  ],\n  \"Analytics\": [\n    \"We may use Google Analytics to monitor Service usage.\",\n    \"• Google Analytics collects non-personally identifying information such as browser type, referring pages, and time spent on the site.\",\n    \"• We do not collect IP addresses through Google Analytics.\",\n    \"• Learn more about Google's privacy practices: https://support.google.com/analytics/answer/4597324?hl=en\"\n  ],\n  \"Changes to This Privacy Policy\": [\n    \"We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.\",\n    \"You are advised to review this Privacy Policy periodically for any changes. Changes are effective when posted on this page.\"\n  ],\n  \"Contact Us\": [\n    \"If you have any questions about this Privacy Policy, please contact us at contact@todiagram.com.\"\n  ]\n}\n"
  },
  {
    "path": "apps/www/src/data/terms.json",
    "content": "{\n  \"Introduction\": [\n    \"Subject to these Terms of Service (“Terms”, “Terms of Service”), jsoncrack.com ('JSON Crack', 'we', 'us' and/or 'our') provides access to JSON Crack's application as a service (collectively, the 'Services'). By using or accessing the Services, you acknowledge that you have read, understand, and agree to be bound by this Agreement.\",\n    \"These Terms of Service govern your use of our web pages located at https://jsoncrack.com.\",\n    \"Our Privacy Policy also governs your use of our Service and explains how we collect, safeguard and disclose information that results from your use of our web pages. Please read it here https://jsoncrack.com/legal/privacy.\",\n    \"Your agreement with us includes these Terms and our Privacy Policy (“Agreements”). You acknowledge that you have read and understood Agreements, and agree to be bound of them.\",\n    \"If you do not agree with (or cannot comply with) Agreements, then you may not use the Service, but please let us know by emailing at contact@todiagram.com so we can try to find a solution. These Terms apply to all visitors, users and others who wish to access or use Service.\",\n    \"Thank you for being responsible.\"\n  ],\n  \"Prohibited Uses\": [\n    \"You may use Service only for lawful purposes and in accordance with Terms. You agree not to use Service:\",\n    \"• In any way that violates any applicable national or international law or regulation.\",\n    \"• For the purpose of exploiting, harming, or attempting to exploit or harm minors in any way by exposing them to inappropriate content or otherwise.\",\n    \"• To transmit, or procure the sending of, any advertising or promotional material, including any “junk mail”, “chain letter,” “spam,” or any other similar solicitation.\",\n    \"• In any way that infringes upon the rights of others, or in any way is illegal, threatening, fraudulent, or harmful, or in connection with any unlawful, illegal, fraudulent, or harmful purpose or activity.\",\n    \"• To engage in any other conduct that restricts or inhibits anyone's use or enjoyment of Service, or which, as determined by us, may harm or offend Company or users of Service or expose them to liability.\",\n    \"Additionally, you agree not to:\",\n    \"• Use Service in any manner that could disable, overburden, damage, or impair Service or interfere with any other party's use of Service, including their ability to engage in real time activities through Service.\",\n    \"• Use any robot, spider, or other automatic device, process, or means to access Service for any purpose, including monitoring or copying any of the material on Service.\",\n    \"• Use any manual process to monitor or copy any of the material on Service or for any other unauthorized purpose without our prior written consent.\",\n    \"• Use any device, software, or routine that interferes with the proper working of Service.\",\n    \"• Introduce any viruses, trojan horses, worms, logic bombs, or other material which is malicious or technologically harmful.\",\n    \"• Attempt to gain unauthorized access to, interfere with, damage, or disrupt any parts of Service, the server on which Service is stored, or any server, computer, or database connected to Service.\",\n    \"• Attack Service via a denial-of-service attack or a distributed denial-of-service attack.\",\n    \"• Take any action that may damage or falsify Company rating.\",\n    \"• Otherwise attempt to interfere with the proper working of Service.\"\n  ],\n  \"Analytics\": [\n    \"We may use third-party Service Providers to monitor and analyze the use of our Service.\",\n    \"Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our Service. This data is shared with other Google services. Google may use the collected data to contextualise and personalise the ads of its own advertising network.\",\n    \"For more information on the privacy practices of Google, please visit the Google Privacy Terms web page: https://policies.google.com/privacy?hl=en\",\n    \"We also encourage you to review the Google's policy for safeguarding your data: https://support.google.com/analytics/answer/6004245\"\n  ],\n  \"Data Security\": [\n    \"As we do not store any user data on our servers, we are not responsible for any data security risks that may occur when users store or process data locally on their devices.\"\n  ],\n  \"Intellectual Property\": [\n    \"Service and its original content (excluding Content provided by users), features and functionality are and will remain the exclusive property of JSON Crack and its licensors. Service is protected by copyright and other laws of foreign countries. Our trademarks and trade dress may not be used in connection with any product or service without the prior written consent of JSON Crack.\"\n  ],\n  \"Disclaimer of Warranty\": [\n    \"THESE SERVICES ARE PROVIDED BY COMPANY ON AN “AS IS” AND “AS AVAILABLE” BASIS. COMPANY MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, AS TO THE OPERATION OF THEIR SERVICES, OR THE INFORMATION, CONTENT OR MATERIALS INCLUDED THEREIN. YOU EXPRESSLY AGREE THAT YOUR USE OF THESE SERVICES, THEIR CONTENT, AND ANY SERVICES OR ITEMS OBTAINED FROM US IS AT YOUR SOLE RISK.\",\n    \"NEITHER COMPANY NOR ANY PERSON ASSOCIATED WITH COMPANY MAKES ANY WARRANTY OR REPRESENTATION WITH RESPECT TO THE COMPLETENESS, SECURITY, RELIABILITY, QUALITY, ACCURACY, OR AVAILABILITY OF THE SERVICES. WITHOUT LIMITING THE FOREGOING, NEITHER COMPANY NOR ANYONE ASSOCIATED WITH COMPANY REPRESENTS OR WARRANTS THAT THE SERVICES, THEIR CONTENT, OR ANY SERVICES OR ITEMS OBTAINED THROUGH THE SERVICES WILL BE ACCURATE, RELIABLE, ERROR-FREE, OR UNINTERRUPTED, THAT DEFECTS WILL BE CORRECTED, THAT THE SERVICES OR THE SERVER THAT MAKES IT AVAILABLE ARE FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS OR THAT THE SERVICES OR ANY SERVICES OR ITEMS OBTAINED THROUGH THE SERVICES WILL OTHERWISE MEET YOUR NEEDS OR EXPECTATIONS.\",\n    \"COMPANY HEREBY DISCLAIMS ALL WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED, STATUTORY, OR OTHERWISE, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND FITNESS FOR PARTICULAR PURPOSE.\",\n    \"THE FOREGOING DOES NOT AFFECT ANY WARRANTIES WHICH CANNOT BE EXCLUDED OR LIMITED UNDER APPLICABLE LAW.\"\n  ],\n  \"Limitation of Liability\": [\n    \"EXCEPT AS PROHIBITED BY LAW, YOU WILL HOLD US AND OUR OFFICERS, DIRECTORS, EMPLOYEES, AND AGENTS HARMLESS FOR ANY INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGE, HOWEVER IT ARISES (INCLUDING ATTORNEYS' FEES AND ALL RELATED COSTS AND EXPENSES OF LITIGATION AND ARBITRATION, OR AT TRIAL OR ON APPEAL, IF ANY, WHETHER OR NOT LITIGATION OR ARBITRATION IS INSTITUTED), WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE, OR OTHER TORTIOUS ACTION, OR ARISING OUT OF OR IN CONNECTION WITH THIS AGREEMENT, INCLUDING WITHOUT LIMITATION ANY CLAIM FOR PERSONAL INJURY OR PROPERTY DAMAGE, ARISING FROM THIS AGREEMENT AND ANY VIOLATION BY YOU OF ANY FEDERAL, STATE, OR LOCAL LAWS, STATUTES, RULES, OR REGULATIONS, EVEN IF COMPANY HAS BEEN PREVIOUSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. EXCEPT AS PROHIBITED BY LAW, IF THERE IS LIABILITY FOUND ON THE PART OF COMPANY, IT WILL BE LIMITED TO THE AMOUNT PAID FOR THE PRODUCTS AND/OR SERVICES, AND UNDER NO CIRCUMSTANCES WILL THERE BE CONSEQUENTIAL OR PUNITIVE DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF PUNITIVE, INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE PRIOR LIMITATION OR EXCLUSION MAY NOT APPLY TO YOU.\"\n  ],\n  \"Termination\": [\n    \"We may terminate or suspend your account and bar access to Service immediately, without prior notice or liability, under our sole discretion, for any reason whatsoever and without limitation, including but not limited to a breach of Terms.\",\n    \"If you wish to terminate your account, you may simply discontinue using Service.\",\n    \"All provisions of Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability.\"\n  ],\n  \"Changes To Service\": [\n    \"We reserve the right to withdraw or amend our Service, and any service or material we provide via Service, in our sole discretion without notice. We will not be liable if for any reason all or any part of Service is unavailable at any time or for any period. From time to time, we may restrict access to some parts of Service, or the entire Service, to users, including registered users.\"\n  ],\n  \"Amendments To Terms\": [\n    \"We may amend Terms at any time by posting the amended terms on this site. It is your responsibility to review these Terms periodically.\",\n    \"Your continued use of the Platform following the posting of revised Terms means that you accept and agree to the changes. You are expected to check this page frequently so you are aware of any changes, as they are binding on you.\",\n    \"By continuing to access or use our Service after any revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new terms, you are no longer authorized to use Service.\"\n  ],\n  \"Acknowledgement\": [\n    \"BY USING SERVICE OR OTHER SERVICES PROVIDED BY US, YOU ACKNOWLEDGE THAT YOU HAVE READ THESE TERMS OF SERVICE AND AGREE TO BE BOUND BY THEM.\"\n  ],\n  \"Contact Us\": [\n    \"If you have any questions about these terms of service, please contact us:\",\n    \"By email: contact@todiagram.com.\"\n  ]\n}\n"
  },
  {
    "path": "apps/www/src/enums/file.enum.ts",
    "content": "export enum FileFormat {\n  \"JSON\" = \"json\",\n  \"YAML\" = \"yaml\",\n  \"XML\" = \"xml\",\n  \"CSV\" = \"csv\",\n}\n\nexport const formats = [\n  { value: FileFormat.JSON, label: \"JSON\" },\n  { value: FileFormat.YAML, label: \"YAML\" },\n  { value: FileFormat.XML, label: \"XML\" },\n  { value: FileFormat.CSV, label: \"CSV\" },\n];\n\nexport enum TypeLanguage {\n  TypeScript = \"typescript\",\n  TypeScript_Combined = \"typescript/typealias\",\n  Go = \"go\",\n  JSON_SCHEMA = \"json_schema\",\n  Kotlin = \"kotlin\",\n  Rust = \"rust\",\n}\n\nexport const typeOptions = [\n  {\n    label: \"TypeScript\",\n    value: TypeLanguage.TypeScript,\n    lang: \"typescript\",\n  },\n  {\n    label: \"TypeScript (merged)\",\n    value: TypeLanguage.TypeScript_Combined,\n    lang: \"typescript\",\n  },\n  {\n    label: \"Go\",\n    value: TypeLanguage.Go,\n    lang: \"go\",\n  },\n  {\n    label: \"JSON Schema\",\n    value: TypeLanguage.JSON_SCHEMA,\n    lang: \"json\",\n  },\n  {\n    label: \"Kotlin\",\n    value: TypeLanguage.Kotlin,\n    lang: \"kotlin\",\n  },\n  {\n    label: \"Rust\",\n    value: TypeLanguage.Rust,\n    lang: \"rust\",\n  },\n];\n"
  },
  {
    "path": "apps/www/src/enums/viewMode.enum.ts",
    "content": "export enum ViewMode {\n  Graph = \"graph\",\n  Tree = \"tree\",\n}\n"
  },
  {
    "path": "apps/www/src/features/Banner.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport { Anchor, Flex, Button, ActionIcon } from \"@mantine/core\";\nimport { useSessionStorage } from \"@mantine/hooks\";\nimport { MdClose } from \"react-icons/md\";\n\nexport const BANNER_HEIGHT =\n  process.env.NEXT_PUBLIC_DISABLE_EXTERNAL_MODE === \"true\" ? \"0px\" : \"40px\";\n\nconst BANNER_LIST = [\n  \"Save and store your diagrams with ToDiagram\",\n  \"Explore the ToDiagram from the creators of JSON Crack\",\n  \"Generate AI diagrams with single prompt\",\n  \"Try ToDiagram for free, no sign-up required\",\n  \"Edit data directly inside diagrams\",\n  \"Explore larger datasets (up to 50 MB) easily\",\n];\n\nexport const Banner = () => {\n  const ROTATION_INTERVAL = 6000; // ms between label changes\n  const FADE_DURATION = 500; // ms for fade transition\n\n  const [index, setIndex] = useState(0);\n  const [visible, setVisible] = useState(true);\n  const [dismissed, setDismissed] = useSessionStorage({\n    key: \"jsoncrack_banner_dismissed\",\n    defaultValue: false,\n  });\n\n  useEffect(() => {\n    if (dismissed) return;\n\n    let fadeTimeout: ReturnType<typeof setTimeout> | undefined;\n    const intervalId = setInterval(() => {\n      setVisible(false);\n      fadeTimeout = setTimeout(() => {\n        setIndex(i => (i + 1) % BANNER_LIST.length);\n        setVisible(true);\n      }, FADE_DURATION);\n    }, ROTATION_INTERVAL);\n\n    return () => {\n      clearInterval(intervalId);\n      if (fadeTimeout) clearTimeout(fadeTimeout);\n    };\n  }, [dismissed]);\n\n  const handleDismiss = (e: React.MouseEvent) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setDismissed(true);\n  };\n\n  if (dismissed) return null;\n\n  return (\n    <Anchor\n      href=\"https://todiagram.com/editor?utm_source=jsoncrack&utm_medium=top_banner\"\n      target=\"_blank\"\n      rel=\"noopener\"\n      underline=\"never\"\n      style={{ position: \"relative\" }}\n    >\n      <Flex\n        h={BANNER_HEIGHT}\n        justify=\"center\"\n        align=\"center\"\n        fw=\"500\"\n        gap=\"xs\"\n        style={{\n          background: \"linear-gradient(90deg, #FF75B7 0%, #FED761 100%)\",\n          color: \"black\",\n        }}\n      >\n        <span\n          style={{\n            transition: `opacity ${FADE_DURATION}ms ease`,\n            opacity: visible ? 1 : 0,\n            willChange: \"opacity\",\n            display: \"inline-block\",\n          }}\n        >\n          {BANNER_LIST[index]}{\" \"}\n        </span>\n        <Button size=\"xs\" color=\"gray\">\n          Try now\n        </Button>\n        <ActionIcon\n          onClick={handleDismiss}\n          size=\"sm\"\n          variant=\"transparent\"\n          style={{\n            position: \"absolute\",\n            right: \"8px\",\n            color: \"black\",\n          }}\n          aria-label=\"Close banner\"\n        >\n          <MdClose size={18} />\n        </ActionIcon>\n      </Flex>\n    </Anchor>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/BottomBar.tsx",
    "content": "import React from \"react\";\nimport { Flex, Menu, Popover, Text } from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { BiSolidDockLeft } from \"react-icons/bi\";\nimport { IoMdCheckmark } from \"react-icons/io\";\nimport { MdArrowUpward } from \"react-icons/md\";\nimport { VscCheck, VscError, VscRunAll, VscSync, VscSyncIgnored } from \"react-icons/vsc\";\nimport { formats } from \"../../enums/file.enum\";\nimport useConfig from \"../../store/useConfig\";\nimport useFile from \"../../store/useFile\";\nimport useGraph from \"./views/GraphView/stores/useGraph\";\n\nconst StyledBottomBar = styled.div`\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};\n  background: ${({ theme }) => theme.TOOLBAR_BG};\n  max-height: 27px;\n  height: 27px;\n  z-index: 35;\n  padding-right: 6px;\n\n  @media screen and (max-width: 320px) {\n    display: none;\n  }\n`;\n\nconst StyledLeft = styled.div`\n  display: flex;\n  align-items: center;\n  justify-content: left;\n  gap: 4px;\n  padding-left: 8px;\n\n  @media screen and (max-width: 480px) {\n    display: none;\n  }\n`;\n\nconst StyledRight = styled.div`\n  display: flex;\n  align-items: center;\n  justify-content: right;\n  gap: 4px;\n`;\n\nconst StyledBottomBarItem = styled.button<{ $bg?: string }>`\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  width: fit-content;\n  margin: 0;\n  height: 28px;\n  padding: 4px;\n  font-size: 12px;\n  font-weight: 400;\n  color: ${({ theme }) => theme.INTERACTIVE_NORMAL};\n  background: ${({ $bg }) => $bg};\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  overflow: hidden;\n\n  &:hover:not(&:disabled) {\n    background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);\n    color: ${({ theme }) => theme.INTERACTIVE_HOVER};\n  }\n\n  &:disabled {\n    opacity: 0.6;\n    cursor: default;\n  }\n`;\n\nexport const BottomBar = () => {\n  const data = useFile(state => state.fileData);\n  const toggleLiveTransform = useConfig(state => state.toggleLiveTransform);\n  const liveTransformEnabled = useConfig(state => state.liveTransformEnabled);\n  const error = useFile(state => state.error);\n  const setContents = useFile(state => state.setContents);\n  const toggleFullscreen = useGraph(state => state.toggleFullscreen);\n  const fullscreen = useGraph(state => state.fullscreen);\n  const setFormat = useFile(state => state.setFormat);\n  const currentFormat = useFile(state => state.format);\n\n  const toggleEditor = () => {\n    toggleFullscreen(!fullscreen);\n    gaEvent(\"toggle_fullscreen\");\n  };\n\n  React.useEffect(() => {\n    if (data?.name) window.document.title = `${data.name} | JSON Crack`;\n  }, [data]);\n\n  return (\n    <StyledBottomBar>\n      <StyledLeft>\n        <StyledBottomBarItem onClick={toggleEditor}>\n          <BiSolidDockLeft />\n        </StyledBottomBarItem>\n        <StyledBottomBarItem>\n          {error ? (\n            <Popover width=\"auto\" shadow=\"md\" position=\"top\" withArrow>\n              <Popover.Target>\n                <Flex align=\"center\" gap={2}>\n                  <VscError color=\"red\" />\n                  <Text c=\"red\" fw={500} fz=\"xs\">\n                    Invalid\n                  </Text>\n                </Flex>\n              </Popover.Target>\n              <Popover.Dropdown style={{ pointerEvents: \"none\" }}>\n                <Text size=\"xs\">{error}</Text>\n              </Popover.Dropdown>\n            </Popover>\n          ) : (\n            <Flex align=\"center\" gap={2}>\n              <VscCheck />\n              <Text size=\"xs\">Valid</Text>\n            </Flex>\n          )}\n        </StyledBottomBarItem>\n        <StyledBottomBarItem\n          onClick={() => {\n            toggleLiveTransform(!liveTransformEnabled);\n            gaEvent(\"toggle_live_transform\");\n          }}\n        >\n          {liveTransformEnabled ? <VscSync /> : <VscSyncIgnored />}\n          <Text fz=\"xs\">Live Transform</Text>\n        </StyledBottomBarItem>\n        {!liveTransformEnabled && (\n          <StyledBottomBarItem onClick={() => setContents({})} disabled={!!error}>\n            <VscRunAll />\n            Click to Transform\n          </StyledBottomBarItem>\n        )}\n      </StyledLeft>\n\n      <StyledRight>\n        <Menu offset={8}>\n          <Menu.Target>\n            <StyledBottomBarItem>\n              <Flex align=\"center\" gap={2}>\n                <MdArrowUpward />\n                <Text size=\"xs\">{currentFormat?.toUpperCase()}</Text>\n              </Flex>\n            </StyledBottomBarItem>\n          </Menu.Target>\n          <Menu.Dropdown>\n            {formats.map(format => (\n              <Menu.Item\n                key={format.value}\n                fz={12}\n                onClick={() => setFormat(format.value)}\n                rightSection={currentFormat === format.value && <IoMdCheckmark />}\n              >\n                {format.label}\n              </Menu.Item>\n            ))}\n          </Menu.Dropdown>\n        </Menu>\n      </StyledRight>\n    </StyledBottomBar>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/ExternalMode.tsx",
    "content": "import React from \"react\";\nimport { Accordion, Anchor, Code, Flex, FocusTrap, Group, Modal, Text } from \"@mantine/core\";\n\nconst ExternalMode = () => {\n  const [isExternal, setExternal] = React.useState(false);\n\n  React.useEffect(() => {\n    if (process.env.NEXT_PUBLIC_DISABLE_EXTERNAL_MODE === \"false\") {\n      if (typeof window !== \"undefined\") {\n        if (window.location.pathname.includes(\"widget\")) return setExternal(false);\n        if (window.location.host !== \"jsoncrack.com\") return setExternal(true);\n        return setExternal(false);\n      }\n    }\n  }, []);\n\n  if (!isExternal) return null;\n\n  return (\n    <Modal\n      title=\"Thanks for using JSON Crack\"\n      opened={isExternal}\n      onClose={() => setExternal(false)}\n      centered\n      size=\"lg\"\n    >\n      <FocusTrap.InitialFocus />\n      <Group>\n        <Accordion variant=\"separated\" w=\"100%\">\n          <Accordion.Item value=\"1\">\n            <Accordion.Control>How can I change the file size limit?</Accordion.Control>\n            <Accordion.Panel>\n              The main reason for the file size limit is to prevent performance issues, not to push\n              you to upgrade. You can increase the limit by setting{\" \"}\n              <Code>NEXT_PUBLIC_NODE_LIMIT</Code> in your <Code>.env</Code> file.\n              <br />\n              <br />\n              If you&apos;d like to work with even larger files and unlock additional features, you\n              can upgrade to the{\" \"}\n              <Anchor\n                href=\"https://todiagram.com?utm_source=jsoncrack&utm_medium=external-mode\"\n                rel=\"noopener\"\n                target=\"_blank\"\n              >\n                Pro\n              </Anchor>{\" \"}\n              version.\n            </Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value=\"2\">\n            <Accordion.Control>How can I stop this dialog from appearing?</Accordion.Control>\n            <Accordion.Panel>\n              You can disable this dialog by setting <Code>NEXT_PUBLIC_DISABLE_EXTERNAL_MODE</Code>{\" \"}\n              to <Code>true</Code> in your <Code>.env.development</Code> file.\n              <br />\n              <br />\n              If you want to re-enable it, simply remove or set the value to <Code>false</Code>.\n            </Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value=\"3\">\n            <Accordion.Control>What are the license terms?</Accordion.Control>\n            <Accordion.Panel>\n              Read the full license terms on{\" \"}\n              <Anchor\n                href=\"https://github.com/AykutSarac/jsoncrack.com/blob/main/LICENSE.md\"\n                rel=\"noopener\"\n                target=\"_blank\"\n              >\n                GitHub\n              </Anchor>\n              .\n            </Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value=\"4\">\n            <Accordion.Control>How do I report a bug or request a feature?</Accordion.Control>\n            <Accordion.Panel>\n              You can report bugs or request features by opening an issue on our{\" \"}\n              <Anchor\n                href=\"https://github.com/AykutSarac/jsoncrack.com/issues\"\n                rel=\"noopener\"\n                target=\"_blank\"\n              >\n                GitHub Issues page\n              </Anchor>\n              .\n              <br />\n              <br />\n              Please provide as much detail as possible to help us address your feedback quickly.\n            </Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value=\"5\">\n            <Accordion.Control>How do I contribute to the project?</Accordion.Control>\n            <Accordion.Panel>\n              We welcome contributions! Visit our{\" \"}\n              <Anchor\n                href=\"https://github.com/AykutSarac/jsoncrack.com\"\n                rel=\"noopener\"\n                target=\"_blank\"\n              >\n                GitHub repository\n              </Anchor>{\" \"}\n              and read the{\" \"}\n              <Anchor\n                href=\"https://github.com/AykutSarac/jsoncrack.com/blob/main/CONTRIBUTING.md\"\n                rel=\"noopener\"\n                target=\"_blank\"\n              >\n                contributing guide\n              </Anchor>{\" \"}\n              to get started.\n            </Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value=\"6\">\n            <Accordion.Control>\n              What is the difference between JSON Crack and ToDiagram?\n            </Accordion.Control>\n            <Accordion.Panel>\n              JSON Crack is a free and open-source tool for visualizing JSON data. ToDiagram is the\n              professional version that offers advanced features, higher limits, and the ability to\n              edit data directly from diagrams. You can learn more or upgrade at{\" \"}\n              <Anchor\n                href=\"https://todiagram.com?utm_source=jsoncrack&utm_medium=external-mode\"\n                rel=\"noopener\"\n                target=\"_blank\"\n              >\n                todiagram.com\n              </Anchor>\n              .\n            </Accordion.Panel>\n          </Accordion.Item>\n        </Accordion>\n      </Group>\n      <Flex justify=\"center\" align=\"center\" gap=\"sm\" mt=\"md\">\n        <Anchor\n          href=\"https://github.com/AykutSarac/jsoncrack.com\"\n          rel=\"noopener\"\n          target=\"_blank\"\n          fz=\"sm\"\n        >\n          GitHub\n        </Anchor>\n        <Text c=\"dimmed\">•</Text>\n        <Anchor\n          href=\"https://todiagram.com?utm_source=jsoncrack&utm_medium=external-mode\"\n          rel=\"noopener\"\n          target=\"_blank\"\n          fz=\"sm\"\n        >\n          ToDiagram\n        </Anchor>\n        <Text c=\"dimmed\">•</Text>\n        <Anchor href=\"https://x.com/aykutsarach\" rel=\"noopener\" target=\"_blank\" fz=\"sm\">\n          Aykut Saraç (@aykutsarach)\n        </Anchor>\n      </Flex>\n    </Modal>\n  );\n};\n\nexport default ExternalMode;\n"
  },
  {
    "path": "apps/www/src/features/editor/FullscreenDropzone.tsx",
    "content": "import React from \"react\";\nimport { Group, Text } from \"@mantine/core\";\nimport { Dropzone } from \"@mantine/dropzone\";\nimport toast from \"react-hot-toast\";\nimport { VscCircleSlash, VscFiles } from \"react-icons/vsc\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport useFile from \"../../store/useFile\";\n\nexport const FullscreenDropzone = () => {\n  const setContents = useFile(state => state.setContents);\n\n  return (\n    <Dropzone.FullScreen\n      maxFiles={1}\n      accept={[\"application/json\", \"application/x-yaml\", \"text/csv\", \"application/xml\"]}\n      onReject={files => toast.error(`Unable to load file ${files[0].file.name}`)}\n      onDrop={async e => {\n        try {\n          const fileContent = await e[0].text();\n          let fileExtension = e[0].name.split(\".\").pop() as FileFormat | undefined;\n          if (!fileExtension) fileExtension = FileFormat.JSON;\n          setContents({ contents: fileContent, format: fileExtension, hasChanges: false });\n        } catch (err) {\n          toast.error(\"An error occurred while reading the file.\");\n          console.error(err);\n        }\n      }}\n    >\n      <Group\n        justify=\"center\"\n        ta=\"center\"\n        align=\"center\"\n        gap=\"xl\"\n        h=\"100vh\"\n        style={{ pointerEvents: \"none\" }}\n      >\n        <Dropzone.Accept>\n          <VscFiles size={100} />\n          <Text fz=\"h1\" fw={500} mt=\"lg\">\n            Upload to JSON Crack\n          </Text>\n          <Text fz=\"lg\" c=\"dimmed\" mt=\"sm\">\n            (Max file size: 300 KB)\n          </Text>\n        </Dropzone.Accept>\n        <Dropzone.Reject>\n          <VscCircleSlash size={100} />\n          <Text fz=\"h1\" fw={500} mt=\"lg\">\n            Invalid file\n          </Text>\n          <Text fz=\"lg\" c=\"dimmed\" mt=\"sm\">\n            Allowed formats are JSON, YAML, CSV, XML\n          </Text>\n        </Dropzone.Reject>\n      </Group>\n    </Dropzone.FullScreen>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/LiveEditor.tsx",
    "content": "import React from \"react\";\nimport { useSessionStorage } from \"@mantine/hooks\";\nimport styled from \"styled-components\";\nimport { ViewMode } from \"../../enums/viewMode.enum\";\nimport { GraphView } from \"./views/GraphView\";\nimport { TreeView } from \"./views/TreeView\";\n\nconst StyledLiveEditor = styled.div`\n  position: relative;\n  height: 100%;\n  background: ${({ theme }) => theme.GRID_BG_COLOR};\n  overflow: auto;\n\n  & > ul {\n    margin-top: 0 !important;\n    padding: 12px !important;\n    font-family: monospace;\n    font-size: 14px;\n    font-weight: 500;\n  }\n\n  .tab-group {\n    position: absolute;\n    top: 10px;\n    left: 10px;\n    z-index: 2;\n  }\n`;\n\nconst View = () => {\n  const [viewMode] = useSessionStorage({\n    key: \"viewMode\",\n    defaultValue: ViewMode.Graph,\n  });\n\n  if (viewMode === ViewMode.Graph) return <GraphView />;\n  if (viewMode === ViewMode.Tree) return <TreeView />;\n  return null;\n};\n\nconst LiveEditor = () => {\n  return (\n    <StyledLiveEditor onContextMenuCapture={e => e.preventDefault()}>\n      <View />\n    </StyledLiveEditor>\n  );\n};\n\nexport default LiveEditor;\n"
  },
  {
    "path": "apps/www/src/features/editor/TextEditor.tsx",
    "content": "import React, { useCallback } from \"react\";\nimport { LoadingOverlay } from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport Editor, { type EditorProps, loader, type OnMount, useMonaco } from \"@monaco-editor/react\";\nimport useConfig from \"../../store/useConfig\";\nimport useFile from \"../../store/useFile\";\n\nloader.config({\n  paths: {\n    vs: \"https://unpkg.com/monaco-editor@0.55.1/min/vs\",\n  },\n});\n\nconst editorOptions: EditorProps[\"options\"] = {\n  tabSize: 2,\n  formatOnType: true,\n  minimap: { enabled: false },\n  stickyScroll: { enabled: false },\n  scrollBeyondLastLine: false,\n  placeholder: \"Start typing...\",\n};\n\nconst TextEditor = () => {\n  const monaco = useMonaco();\n  const contents = useFile(state => state.contents);\n  const setContents = useFile(state => state.setContents);\n  const setError = useFile(state => state.setError);\n  const jsonSchema = useFile(state => state.jsonSchema);\n  const getHasChanges = useFile(state => state.getHasChanges);\n  const theme = useConfig(state => (state.darkmodeEnabled ? \"vs-dark\" : \"light\"));\n  const fileType = useFile(state => state.format);\n  const jsonDefaults = (monaco?.languages as any)?.json?.jsonDefaults as\n    | { setDiagnosticsOptions: (options: unknown) => void }\n    | undefined;\n\n  React.useEffect(() => {\n    if (!jsonDefaults) return;\n\n    jsonDefaults.setDiagnosticsOptions({\n      validate: true,\n      allowComments: true,\n      enableSchemaRequest: true,\n      ...(jsonSchema && {\n        schemas: [\n          {\n            uri: \"http://myserver/foo-schema.json\",\n            fileMatch: [\"*\"],\n            schema: jsonSchema,\n          },\n        ],\n      }),\n    });\n  }, [jsonDefaults, jsonSchema]);\n\n  React.useEffect(() => {\n    const beforeunload = (e: BeforeUnloadEvent) => {\n      if (getHasChanges()) {\n        const confirmationMessage =\n          \"Unsaved changes, if you leave before saving  your changes will be lost\";\n\n        (e || window.event).returnValue = confirmationMessage; //Gecko + IE\n        return confirmationMessage;\n      }\n    };\n\n    window.addEventListener(\"beforeunload\", beforeunload);\n\n    return () => {\n      window.removeEventListener(\"beforeunload\", beforeunload);\n    };\n  }, [getHasChanges]);\n\n  const handleMount: OnMount = useCallback(editor => {\n    editor.onDidPaste(() => {\n      editor.getAction(\"editor.action.formatDocument\")?.run();\n    });\n  }, []);\n\n  return (\n    <StyledEditorWrapper>\n      <StyledWrapper>\n        <Editor\n          height=\"100%\"\n          language={fileType}\n          theme={theme}\n          value={contents}\n          options={editorOptions}\n          onMount={handleMount}\n          onValidate={errors => setError(errors[0]?.message || \"\")}\n          onChange={contents => setContents({ contents, skipUpdate: true })}\n          loading={<LoadingOverlay visible />}\n        />\n      </StyledWrapper>\n    </StyledEditorWrapper>\n  );\n};\n\nexport default TextEditor;\n\nconst StyledEditorWrapper = styled.div`\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  user-select: none;\n`;\n\nconst StyledWrapper = styled.div`\n  display: grid;\n  height: 100%;\n  grid-template-columns: 100%;\n  grid-template-rows: minmax(0, 1fr);\n`;\n"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/FileMenu.tsx",
    "content": "import React from \"react\";\nimport { Flex, Menu } from \"@mantine/core\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { CgChevronDown } from \"react-icons/cg\";\nimport useFile from \"../../../store/useFile\";\nimport { useModal } from \"../../../store/useModal\";\nimport { StyledToolElement } from \"./styles\";\n\nexport const FileMenu = () => {\n  const setVisible = useModal(state => state.setVisible);\n  const getContents = useFile(state => state.getContents);\n  const getFormat = useFile(state => state.getFormat);\n\n  const handleSave = () => {\n    const a = document.createElement(\"a\");\n    const file = new Blob([getContents()], { type: \"text/plain\" });\n\n    a.href = window.URL.createObjectURL(file);\n    a.download = `jsoncrack.${getFormat()}`;\n    a.click();\n\n    gaEvent(\"save_file\", { label: getFormat() });\n  };\n\n  return (\n    <Menu shadow=\"md\" withArrow>\n      <Menu.Target>\n        <StyledToolElement title=\"File\">\n          <Flex align=\"center\" gap={3}>\n            File\n            <CgChevronDown />\n          </Flex>\n        </StyledToolElement>\n      </Menu.Target>\n      <Menu.Dropdown>\n        <Menu.Item onClick={() => setVisible(\"ImportModal\", true)}>Import</Menu.Item>\n        <Menu.Item onClick={handleSave}>Export</Menu.Item>\n      </Menu.Dropdown>\n    </Menu>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/SearchInput.tsx",
    "content": "import React from \"react\";\nimport { Flex, Text, TextInput } from \"@mantine/core\";\nimport { getHotkeyHandler } from \"@mantine/hooks\";\nimport { useOs } from \"@mantine/hooks\";\nimport { AiOutlineSearch } from \"react-icons/ai\";\nimport { useFocusNode } from \"../../../hooks/useFocusNode\";\n\nexport const SearchInput = () => {\n  const [searchValue, setValue, skip, nodeCount, currentNode] = useFocusNode();\n  const os = useOs();\n\n  const coreKey = os === \"macos\" ? \"⌘\" : \"Ctrl\";\n\n  return (\n    <TextInput\n      variant=\"unstyled\"\n      type=\"search\"\n      size=\"xs\"\n      id=\"search-node\"\n      w={180}\n      value={searchValue}\n      onChange={e => setValue(e.currentTarget.value)}\n      placeholder={`Search Node (${coreKey} + F)`}\n      autoComplete=\"off\"\n      autoCorrect=\"off\"\n      onKeyDown={getHotkeyHandler([[\"Enter\", skip]])}\n      leftSection={<AiOutlineSearch />}\n      rightSection={\n        searchValue && (\n          <Flex h={30} align=\"center\">\n            <Text size=\"xs\" c=\"dimmed\" pr=\"md\">\n              {searchValue && `${nodeCount}/${nodeCount > 0 ? currentNode + 1 : \"0\"}`}\n            </Text>\n          </Flex>\n        )\n      }\n      style={{ borderBottom: \"1px solid gray\" }}\n    />\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/ThemeToggle.tsx",
    "content": "import { FaMoon, FaSun } from \"react-icons/fa6\";\nimport useConfig from \"../../../store/useConfig\";\nimport { StyledToolElement } from \"./styles\";\n\nexport const ThemeToggle = () => {\n  const darkmodeEnabled = useConfig(state => state.darkmodeEnabled);\n  const toggleDarkMode = useConfig(state => state.toggleDarkMode);\n\n  return (\n    <StyledToolElement title=\"Fullscreen\" onClick={() => toggleDarkMode(!darkmodeEnabled)}>\n      {!darkmodeEnabled ? <FaMoon size=\"18\" /> : <FaSun size=\"18\" />}\n    </StyledToolElement>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/ToolsMenu.tsx",
    "content": "import React from \"react\";\nimport { Menu, Flex } from \"@mantine/core\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { CgChevronDown } from \"react-icons/cg\";\nimport { MdFilterListAlt } from \"react-icons/md\";\nimport { VscSearchFuzzy, VscJson, VscGroupByRefType } from \"react-icons/vsc\";\nimport { useModal } from \"../../../store/useModal\";\nimport { StyledToolElement } from \"./styles\";\n\nexport const ToolsMenu = () => {\n  const setVisible = useModal(state => state.setVisible);\n\n  return (\n    <Menu shadow=\"md\" withArrow>\n      <Menu.Target>\n        <StyledToolElement onClick={() => gaEvent(\"show_tools_menu\")}>\n          <Flex align=\"center\" gap={3}>\n            Tools <CgChevronDown />\n          </Flex>\n        </StyledToolElement>\n      </Menu.Target>\n      <Menu.Dropdown>\n        <Menu.Item\n          leftSection={<VscSearchFuzzy />}\n          onClick={() => {\n            setVisible(\"JQModal\", true);\n            gaEvent(\"open_jq_modal\");\n          }}\n        >\n          JSON Query (jq)\n        </Menu.Item>\n        <Menu.Item\n          leftSection={<MdFilterListAlt />}\n          onClick={() => {\n            setVisible(\"JPathModal\", true);\n            gaEvent(\"open_json_path_modal\");\n          }}\n        >\n          JSON Path\n        </Menu.Item>\n        <Menu.Item\n          leftSection={<VscJson />}\n          onClick={() => {\n            setVisible(\"SchemaModal\", true);\n            gaEvent(\"open_schema_modal\");\n          }}\n        >\n          JSON Schema\n        </Menu.Item>\n        <Menu.Divider />\n        <Menu.Item\n          leftSection={<VscGroupByRefType />}\n          onClick={() => {\n            setVisible(\"TypeModal\", true);\n            gaEvent(\"open_type_modal\");\n          }}\n        >\n          Generate Type\n        </Menu.Item>\n      </Menu.Dropdown>\n    </Menu>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/ViewMenu.tsx",
    "content": "import { Menu, Flex, SegmentedControl } from \"@mantine/core\";\nimport { useSessionStorage } from \"@mantine/hooks\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { CgChevronDown } from \"react-icons/cg\";\nimport { ViewMode } from \"../../../enums/viewMode.enum\";\nimport { StyledToolElement } from \"./styles\";\n\nexport const ViewMenu = () => {\n  const [viewMode, setViewMode] = useSessionStorage({\n    key: \"viewMode\",\n    defaultValue: ViewMode.Graph,\n  });\n\n  return (\n    <Menu shadow=\"md\" closeOnItemClick={false} withArrow>\n      <Menu.Target>\n        <StyledToolElement onClick={() => gaEvent(\"show_view_menu\")}>\n          <Flex align=\"center\" gap={3}>\n            View <CgChevronDown />\n          </Flex>\n        </StyledToolElement>\n      </Menu.Target>\n      <Menu.Dropdown>\n        <SegmentedControl\n          size=\"md\"\n          w=\"100%\"\n          value={viewMode}\n          onChange={e => {\n            setViewMode(e as ViewMode);\n            gaEvent(\"change_view_mode\", { label: e });\n          }}\n          data={[\n            { value: ViewMode.Graph, label: \"Graph\" },\n            { value: ViewMode.Tree, label: \"Tree\" },\n          ]}\n          fullWidth\n          orientation=\"vertical\"\n        />\n      </Menu.Dropdown>\n    </Menu>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/index.tsx",
    "content": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Flex, Group } from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport toast from \"react-hot-toast\";\nimport { AiOutlineFullscreen } from \"react-icons/ai\";\nimport { FaGithub } from \"react-icons/fa6\";\nimport { JSONCrackLogo } from \"../../../layout/JSONCrackBrandLogo\";\nimport { FileMenu } from \"./FileMenu\";\nimport { ThemeToggle } from \"./ThemeToggle\";\nimport { ToolsMenu } from \"./ToolsMenu\";\nimport { ViewMenu } from \"./ViewMenu\";\nimport { StyledToolElement } from \"./styles\";\n\nconst StyledTools = styled.div`\n  position: relative;\n  display: flex;\n  width: 100%;\n  align-items: center;\n  gap: 4px;\n  justify-content: space-between;\n  height: 45px;\n  padding: 6px 12px;\n  background: ${({ theme }) => theme.TOOLBAR_BG};\n  color: ${({ theme }) => theme.SILVER};\n  z-index: 36;\n  border-bottom: 1px solid ${({ theme }) => theme.SILVER_DARK};\n\n  @media only screen and (max-width: 320px) {\n    display: none;\n  }\n`;\n\nfunction fullscreenBrowser() {\n  if (!document.fullscreenElement) {\n    document.documentElement.requestFullscreen().catch(() => {\n      toast.error(\"Unable to enter fullscreen mode.\");\n    });\n  } else if (document.exitFullscreen) {\n    document.exitFullscreen();\n  }\n}\n\nexport const Toolbar = () => {\n  return (\n    <StyledTools>\n      <Group gap=\"xs\" justify=\"left\" w=\"100%\" style={{ flexWrap: \"nowrap\" }}>\n        <StyledToolElement title=\"JSON Crack\">\n          <Flex gap=\"xs\" align=\"center\" justify=\"center\">\n            <JSONCrackLogo fontSize=\"14px\" hideLogo />\n          </Flex>\n        </StyledToolElement>\n        <FileMenu />\n        <ViewMenu />\n        <ToolsMenu />\n      </Group>\n      <Group gap=\"xs\" justify=\"right\" w=\"100%\" style={{ flexWrap: \"nowrap\" }}>\n        <ThemeToggle />\n        <Link href=\"https://github.com/AykutSarac/jsoncrack.com\" rel=\"noopener\" target=\"_blank\">\n          <StyledToolElement title=\"GitHub\">\n            <FaGithub size=\"20\" />\n          </StyledToolElement>\n        </Link>\n        <StyledToolElement title=\"Fullscreen\" onClick={fullscreenBrowser}>\n          <AiOutlineFullscreen size=\"20\" />\n        </StyledToolElement>\n      </Group>\n    </StyledTools>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/styles.ts",
    "content": "import styled from \"styled-components\";\n\nexport const StyledToolElement = styled.button<{ $hide?: boolean; $highlight?: boolean }>`\n  display: ${({ $hide }) => ($hide ? \"none\" : \"flex\")};\n  align-items: center;\n  gap: 4px;\n  place-content: center;\n  font-size: 14px;\n  background: ${({ $highlight }) =>\n    $highlight ? \"linear-gradient(rgba(0, 0, 0, 0.1) 0 0)\" : \"none\"};\n  color: ${({ theme }) => theme.INTERACTIVE_NORMAL};\n  padding: 6px;\n  border-radius: 3px;\n  white-space: nowrap;\n\n  &:hover {\n    background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);\n  }\n\n  &:hover {\n    color: ${({ theme }) => theme.INTERACTIVE_HOVER};\n    opacity: 1;\n    box-shadow: none;\n  }\n`;\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomEdge/index.tsx",
    "content": "import React from \"react\";\nimport { useComputedColorScheme } from \"@mantine/core\";\nimport type { EdgeProps } from \"reaflow\";\nimport { Edge } from \"reaflow\";\nimport useGraph from \"../stores/useGraph\";\n\nconst CustomEdgeWrapper = (props: EdgeProps) => {\n  const colorScheme = useComputedColorScheme();\n  const viewPort = useGraph(state => state.viewPort);\n  const [hovered, setHovered] = React.useState(false);\n\n  const handeClick = () => {\n    const targetNodeId = (props.properties as { to?: string } | undefined)?.to;\n    const targetNodeDom = document.querySelector(\n      `[data-id$=\"node-${targetNodeId}\"]`\n    ) as HTMLElement;\n    if (targetNodeDom && targetNodeDom.parentElement) {\n      viewPort?.camera.centerFitElementIntoView(targetNodeDom.parentElement, {\n        elementExtraMarginForZoom: 150,\n      });\n    }\n  };\n\n  return (\n    <Edge\n      containerClassName={`edge-${props.id}`}\n      onClick={handeClick}\n      onEnter={() => setHovered(true)}\n      onLeave={() => setHovered(false)}\n      style={{\n        stroke: colorScheme === \"dark\" ? \"#444444\" : \"#BCBEC0\",\n        ...(hovered && { stroke: \"#3B82F6\" }),\n        strokeWidth: 1.5,\n      }}\n      {...props}\n    />\n  );\n};\n\nexport const CustomEdge = React.memo(CustomEdgeWrapper);\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/ObjectNode.tsx",
    "content": "import React from \"react\";\nimport type { NodeData } from \"jsoncrack-react\";\nimport type { CustomNodeProps } from \".\";\nimport { NODE_DIMENSIONS } from \"../../../../../constants/graph\";\nimport { TextRenderer } from \"./TextRenderer\";\nimport * as Styled from \"./styles\";\n\ntype RowProps = {\n  row: NodeData[\"text\"][number];\n  x: number;\n  y: number;\n  index: number;\n};\n\nconst Row = ({ row, x, y, index }: RowProps) => {\n  const rowPosition = index * NODE_DIMENSIONS.ROW_HEIGHT;\n\n  const getRowText = () => {\n    if (row.type === \"object\") return `{${row.childrenCount ?? 0} keys}`;\n    if (row.type === \"array\") return `[${row.childrenCount ?? 0} items]`;\n    return row.value;\n  };\n\n  return (\n    <Styled.StyledRow\n      $value={row.value}\n      data-key={`${row.key}: ${row.value}`}\n      data-x={x}\n      data-y={y + rowPosition}\n    >\n      <Styled.StyledKey $type=\"object\">{row.key}: </Styled.StyledKey>\n      <TextRenderer>{getRowText()}</TextRenderer>\n    </Styled.StyledRow>\n  );\n};\n\nconst Node = ({ node, x, y }: CustomNodeProps) => (\n  <Styled.StyledForeignObject\n    data-id={`node-${node.id}`}\n    width={node.width}\n    height={node.height}\n    x={0}\n    y={0}\n    $isObject\n  >\n    {node.text.map((row, index) => (\n      <Row key={`${node.id}-${index}`} row={row} x={x} y={y} index={index} />\n    ))}\n  </Styled.StyledForeignObject>\n);\n\nfunction propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {\n  return (\n    JSON.stringify(prev.node.text) === JSON.stringify(next.node.text) &&\n    prev.node.width === next.node.width\n  );\n}\n\nexport const ObjectNode = React.memo(Node, propsAreEqual);\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/TextNode.tsx",
    "content": "import React from \"react\";\nimport styled from \"styled-components\";\nimport type { CustomNodeProps } from \".\";\nimport { TextRenderer } from \"./TextRenderer\";\nimport * as Styled from \"./styles\";\n\nconst StyledTextNodeWrapper = styled.span<{ $isParent: boolean }>`\n  display: flex;\n  justify-content: ${({ $isParent }) => ($isParent ? \"center\" : \"flex-start\")};\n  align-items: center;\n  height: 100%;\n  width: 100%;\n  overflow: hidden;\n  padding: 0 10px;\n`;\n\nconst Node = ({ node, x, y }: CustomNodeProps) => {\n  const { text, width, height } = node;\n  const value = text[0].value;\n\n  return (\n    <Styled.StyledForeignObject\n      data-id={`node-${node.id}`}\n      width={width}\n      height={height}\n      x={0}\n      y={0}\n    >\n      <StyledTextNodeWrapper\n        data-x={x}\n        data-y={y}\n        data-key={JSON.stringify(text)}\n        $isParent={false}\n      >\n        <Styled.StyledKey $value={value} $type={typeof text[0].value}>\n          <TextRenderer>{value}</TextRenderer>\n        </Styled.StyledKey>\n      </StyledTextNodeWrapper>\n    </Styled.StyledForeignObject>\n  );\n};\n\nfunction propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {\n  return prev.node.text === next.node.text && prev.node.width === next.node.width;\n}\n\nexport const TextNode = React.memo(Node, propsAreEqual);\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/TextRenderer.tsx",
    "content": "import React from \"react\";\nimport { ColorSwatch } from \"@mantine/core\";\nimport styled from \"styled-components\";\n\nconst StyledRow = styled.span`\n  display: inline-flex;\n  align-items: center;\n  overflow: hidden;\n  gap: 4px;\n  vertical-align: middle;\n`;\n\nconst isURL = (word: string) => {\n  const urlPattern =\n    /^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$/gm;\n\n  return word?.match(urlPattern);\n};\n\nconst Linkify = (text: string) => {\n  const addMarkup = (word: string) => {\n    return isURL(word)\n      ? `<a onclick=\"event.stopPropagation()\" href=\"${word}\" style=\"text-decoration: underline; pointer-events: all;\" target=\"_blank\" rel=\"noopener noreferrer\">${word}</a>`\n      : word;\n  };\n\n  const words = text.split(\" \");\n  const formatedWords = words.map(w => addMarkup(w));\n  const html = formatedWords.join(\" \");\n  return <span dangerouslySetInnerHTML={{ __html: html }} />;\n};\n\nexport const TextRenderer = ({ children }: React.PropsWithChildren) => {\n  if (typeof children === \"string\" && isURL(children)) return Linkify(children);\n\n  if (typeof children === \"string\" && isColorFormat(children)) {\n    return (\n      <StyledRow>\n        <ColorSwatch size={12} radius={4} mr={4} color={children} />\n        {children}\n      </StyledRow>\n    );\n  }\n\n  return <>{`${children}`}</>;\n};\n\nfunction isColorFormat(colorString: string) {\n  const hexCodeRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;\n  const rgbRegex = /^rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/;\n  const rgbaRegex = /^rgba\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(0|1|0\\.\\d+)\\s*\\)$/;\n\n  return (\n    hexCodeRegex.test(colorString) || rgbRegex.test(colorString) || rgbaRegex.test(colorString)\n  );\n}\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/index.tsx",
    "content": "import React from \"react\";\nimport { useComputedColorScheme } from \"@mantine/core\";\nimport type { NodeData } from \"jsoncrack-react\";\nimport type { NodeProps } from \"reaflow\";\nimport { Node } from \"reaflow\";\nimport { useModal } from \"../../../../../store/useModal\";\nimport useGraph from \"../stores/useGraph\";\nimport { ObjectNode } from \"./ObjectNode\";\nimport { TextNode } from \"./TextNode\";\n\nexport interface CustomNodeProps {\n  node: NodeData;\n  x: number;\n  y: number;\n  hasCollapse?: boolean;\n}\n\nconst CustomNodeWrapper = (nodeProps: NodeProps<NodeData>) => {\n  const setSelectedNode = useGraph(state => state.setSelectedNode);\n  const setVisible = useModal(state => state.setVisible);\n  const colorScheme = useComputedColorScheme();\n\n  const handleNodeClick = React.useCallback(\n    (_: React.MouseEvent<SVGGElement, MouseEvent>, data: NodeData) => {\n      if (setSelectedNode) setSelectedNode(data);\n      setVisible(\"NodeModal\", true);\n    },\n    [setSelectedNode, setVisible]\n  );\n\n  return (\n    <Node\n      {...nodeProps}\n      onClick={handleNodeClick as any}\n      animated={false}\n      label={null as any}\n      onEnter={ev => {\n        ev.currentTarget.style.stroke = \"#3B82F6\";\n      }}\n      onLeave={ev => {\n        ev.currentTarget.style.stroke = colorScheme === \"dark\" ? \"#424242\" : \"#BCBEC0\";\n      }}\n      style={{\n        fill: colorScheme === \"dark\" ? \"#292929\" : \"#ffffff\",\n        stroke: colorScheme === \"dark\" ? \"#424242\" : \"#BCBEC0\",\n        strokeWidth: 1,\n      }}\n    >\n      {({ node, x, y }) => {\n        const hasKey = nodeProps.properties.text[0].key;\n        if (!hasKey) return <TextNode node={nodeProps.properties as NodeData} x={x} y={y} />;\n\n        return <ObjectNode node={node as NodeData} x={x} y={y} />;\n      }}\n    </Node>\n  );\n};\n\nexport const CustomNode = React.memo(CustomNodeWrapper);\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/styles.tsx",
    "content": "import type { DefaultTheme } from \"styled-components\";\nimport styled from \"styled-components\";\nimport { LinkItUrl } from \"react-linkify-it\";\nimport { NODE_DIMENSIONS } from \"../../../../../constants/graph\";\n\ntype TextColorFn = {\n  theme: DefaultTheme;\n  $type?: string;\n  $value?: string | number | null | boolean;\n};\n\nfunction getTextColor({ $value, $type, theme }: TextColorFn) {\n  if ($value === null) return theme.NODE_COLORS.NULL;\n  if ($type === \"object\") return theme.NODE_COLORS.NODE_KEY;\n  if ($type === \"number\") return theme.NODE_COLORS.INTEGER;\n  if ($value === true) return theme.NODE_COLORS.BOOL.TRUE;\n  if ($value === false) return theme.NODE_COLORS.BOOL.FALSE;\n  return theme.NODE_COLORS.NODE_VALUE;\n}\n\nexport const StyledLinkItUrl = styled(LinkItUrl)`\n  text-decoration: underline;\n  pointer-events: all;\n`;\n\nexport const StyledForeignObject = styled.foreignObject<{ $isObject?: boolean }>`\n  text-align: ${({ $isObject }) => !$isObject && \"center\"};\n  color: ${({ theme }) => theme.NODE_COLORS.TEXT};\n  font-family: monospace;\n  font-size: 12px;\n  font-weight: 500;\n  overflow: hidden;\n  pointer-events: none;\n\n  &.searched {\n    background: rgba(27, 255, 0, 0.1);\n    border: 1px solid ${({ theme }) => theme.TEXT_POSITIVE};\n    border-radius: 2px;\n    box-sizing: border-box;\n  }\n\n  .highlight {\n    background: rgba(255, 214, 0, 0.15);\n  }\n\n  .renderVisible {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    font-size: 12px;\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n    cursor: pointer;\n  }\n`;\n\nexport const StyledKey = styled.span<{\n  $type: TextColorFn[\"$type\"];\n  $value?: TextColorFn[\"$value\"];\n}>`\n  display: inline;\n  align-items: center;\n  justify-content: center;\n  flex: 1;\n  min-width: 0;\n  height: auto;\n  line-height: inherit;\n  padding: 0; // Remove padding\n  color: ${({ theme, $type, $value = \"\" }) => getTextColor({ $value, $type, theme })};\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n`;\n\nexport const StyledRow = styled.span<{ $value: TextColorFn[\"$value\"] }>`\n  padding: 3px 10px;\n  height: ${NODE_DIMENSIONS.ROW_HEIGHT}px;\n  line-height: 24px;\n  color: ${({ theme, $value }) => getTextColor({ $value, theme, $type: typeof $value })};\n  display: block;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  border-bottom: 1px solid ${({ theme }) => theme.NODE_COLORS.DIVIDER};\n  box-sizing: border-box;\n\n  &:last-of-type {\n    border-bottom: none;\n  }\n\n  .searched & {\n    border-bottom: 1px solid ${({ theme }) => theme.TEXT_POSITIVE};\n  }\n`;\n\nexport const StyledChildrenCount = styled.span`\n  color: ${({ theme }) => theme.NODE_COLORS.CHILD_COUNT};\n  padding: 10px;\n  margin-left: -15px;\n`;\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/NotSupported.tsx",
    "content": "import React from \"react\";\nimport { Anchor, Button, Image, Overlay, Stack, Text } from \"@mantine/core\";\nimport styled, { keyframes } from \"styled-components\";\nimport useConfig from \"../../../../store/useConfig\";\n\nconst shineEffect = keyframes`\n  0% {\n    transform: translateX(-120%) rotate(25deg);\n    opacity: 0.5;\n  }\n  5% {\n    opacity: 0.5;\n    transform: translateX(-80%) rotate(25deg);\n  }\n  70% {\n    transform: translateX(80%) rotate(25deg);\n    opacity: 0.5;\n  }\n  80% {\n    transform: translateX(120%) rotate(25deg);\n    opacity: 0;\n  }\n  100% {\n    transform: translateX(120%) rotate(25deg);\n    opacity: 0;\n  }\n`;\n\nconst ShiningButton = styled.div`\n  position: relative;\n  overflow: hidden;\n  display: inline-block;\n  border-radius: 0.5rem;\n  z-index: 10;\n\n  &::before {\n    content: \"\";\n    position: absolute;\n    top: -50%;\n    left: -50%;\n    width: 200%;\n    height: 200%;\n    background: linear-gradient(\n      to right,\n      rgba(255, 255, 255, 0) 0%,\n      rgba(255, 255, 255, 0) 35%,\n      rgba(255, 255, 255, 0.5) 50%,\n      rgba(255, 255, 255, 0) 65%,\n      rgba(255, 255, 255, 0) 100%\n    );\n    transform: translateX(-120%) rotate(25deg);\n    z-index: 20;\n    pointer-events: none;\n    animation: ${shineEffect} 4s ease-out infinite;\n    transition: transform 0.2s ease-out;\n  }\n`;\n\nexport const NotSupported = () => {\n  const darkmodeEnabled = useConfig(state => state.darkmodeEnabled);\n\n  return (\n    <Overlay\n      backgroundOpacity={0.8}\n      color={darkmodeEnabled ? \"gray\" : \"rgb(226, 240, 243)\"}\n      blur=\"1.5\"\n      center\n    >\n      <Stack maw=\"60%\" align=\"center\" justify=\"center\" gap=\"sm\">\n        <Image src=\"https://todiagram.com/logo.svg\" alt=\"Unsupported\" w={72} h={72} />\n        <Text fz=\"48\" fw={600} c=\"bright\">\n          Time to upgrade!\n        </Text>\n        <Text ta=\"center\" size=\"lg\" fw={500} c=\"gray\" maw=\"600\">\n          This diagram is too large and not supported at JSON Crack.\n          <br />\n          Try{\" \"}\n          <Anchor\n            inherit\n            c=\"teal\"\n            fw=\"500\"\n            href=\"https://todiagram.com/editor?utm_source=jsoncrack&utm_medium=data_limit\"\n            target=\"_blank\"\n            rel=\"noopener\"\n          >\n            ToDiagram\n          </Anchor>{\" \"}\n          for larger diagrams and more features.\n        </Text>\n        <ShiningButton style={{ marginTop: \"16px\", position: \"relative\" }}>\n          <Button\n            component=\"a\"\n            href=\"https://todiagram.com/editor?utm_source=jsoncrack&utm_medium=data_limit\"\n            rel=\"noopener\"\n            size=\"lg\"\n            w=\"200\"\n            target=\"_blank\"\n            color=\"teal\"\n          >\n            Try now &rarr;\n          </Button>\n        </ShiningButton>\n      </Stack>\n    </Overlay>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/OptionsMenu.tsx",
    "content": "import React from \"react\";\nimport { ActionIcon, Flex, Menu, Text } from \"@mantine/core\";\nimport { useHotkeys } from \"@mantine/hooks\";\nimport styled from \"styled-components\";\nimport type { LayoutDirection } from \"jsoncrack-react\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { BsCheck2 } from \"react-icons/bs\";\nimport { LuImageDown, LuMenu } from \"react-icons/lu\";\nimport { TiFlowMerge } from \"react-icons/ti\";\nimport useConfig from \"../../../../store/useConfig\";\nimport { useModal } from \"../../../../store/useModal\";\nimport useGraph from \"./stores/useGraph\";\n\nconst StyledFlowIcon = styled(TiFlowMerge)<{ rotate: number }>`\n  transform: rotate(${({ rotate }) => `${rotate}deg`});\n`;\n\nconst getNextDirection = (direction: LayoutDirection) => {\n  if (direction === \"RIGHT\") return \"DOWN\";\n  if (direction === \"DOWN\") return \"LEFT\";\n  if (direction === \"LEFT\") return \"UP\";\n  return \"RIGHT\";\n};\n\nconst rotateLayout = (direction: LayoutDirection) => {\n  if (direction === \"LEFT\") return 90;\n  if (direction === \"UP\") return 180;\n  if (direction === \"RIGHT\") return 270;\n  return 360;\n};\n\nexport const OptionsMenu = () => {\n  const toggleGestures = useConfig(state => state.toggleGestures);\n  const toggleRulers = useConfig(state => state.toggleRulers);\n  const gesturesEnabled = useConfig(state => state.gesturesEnabled);\n  const rulersEnabled = useConfig(state => state.rulersEnabled);\n  const setDirection = useGraph(state => state.setDirection);\n  const direction = useGraph(state => state.direction);\n  const setVisible = useModal(state => state.setVisible);\n  const [coreKey, setCoreKey] = React.useState(\"CTRL\");\n\n  const toggleDirection = () => {\n    const nextDirection = getNextDirection(direction || \"RIGHT\");\n    if (setDirection) setDirection(nextDirection);\n  };\n\n  useHotkeys(\n    [\n      [\"mod+shift+d\", toggleDirection],\n      [\n        \"mod+f\",\n        () => {\n          const input = document.querySelector(\"#search-node\") as HTMLInputElement;\n          input.focus();\n        },\n      ],\n    ],\n    []\n  );\n\n  React.useEffect(() => {\n    if (typeof window !== \"undefined\") {\n      setCoreKey(navigator.userAgent.indexOf(\"Mac OS X\") ? \"⌘\" : \"CTRL\");\n    }\n  }, []);\n\n  return (\n    <Flex\n      gap=\"xs\"\n      align=\"center\"\n      style={{\n        position: \"absolute\",\n        top: \"10px\",\n        left: \"10px\",\n        zIndex: 100,\n      }}\n    >\n      <Menu withArrow>\n        <Menu.Target>\n          <ActionIcon aria-label=\"actions\" size=\"lg\" color=\"gray\" variant=\"light\">\n            <LuMenu size=\"18\" />\n          </ActionIcon>\n        </Menu.Target>\n        <Menu.Dropdown>\n          <Menu.Item\n            fz=\"sm\"\n            leftSection={<LuImageDown color=\"gray\" />}\n            onClick={() => setVisible(\"DownloadModal\", true)}\n          >\n            <Flex justify=\"space-between\" gap=\"md\">\n              <Text inherit>Export</Text>\n              <Text fz=\"xs\" ml=\"md\" c=\"dimmed\">\n                {coreKey} + S\n              </Text>\n            </Flex>\n          </Menu.Item>\n          <Menu.Item\n            fz=\"sm\"\n            onClick={() => {\n              toggleDirection();\n              gaEvent(\"rotate_layout\", { label: direction });\n            }}\n            leftSection={\n              <StyledFlowIcon color=\"gray\" rotate={rotateLayout(direction || \"RIGHT\")} />\n            }\n            rightSection={\n              <Text fz=\"xs\" ml=\"md\" c=\"dimmed\">\n                {coreKey} Shift D\n              </Text>\n            }\n            closeMenuOnClick={false}\n          >\n            Rotate Layout\n          </Menu.Item>\n          <Menu.Divider />\n          <Menu.Sub position=\"right\" offset={0}>\n            <Menu.Sub.Target>\n              <Menu.Sub.Item fz=\"sm\">View Options</Menu.Sub.Item>\n            </Menu.Sub.Target>\n            <Menu.Sub.Dropdown>\n              <Menu.Item\n                fz=\"sm\"\n                leftSection={<BsCheck2 opacity={rulersEnabled ? 100 : 0} />}\n                onClick={() => {\n                  toggleRulers(!rulersEnabled);\n                  gaEvent(\"toggle_rulers\", { label: rulersEnabled ? \"on\" : \"off\" });\n                }}\n                closeMenuOnClick={false}\n              >\n                Rulers\n              </Menu.Item>\n              <Menu.Item\n                fz=\"sm\"\n                leftSection={<BsCheck2 opacity={gesturesEnabled ? 100 : 0} />}\n                onClick={() => {\n                  toggleGestures(!gesturesEnabled);\n                  gaEvent(\"toggle_gestures\", { label: gesturesEnabled ? \"on\" : \"off\" });\n                }}\n              >\n                Zoom on Scroll\n              </Menu.Item>\n            </Menu.Sub.Dropdown>\n          </Menu.Sub>\n        </Menu.Dropdown>\n      </Menu>\n    </Flex>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/SecureInfo.tsx",
    "content": "import React from \"react\";\nimport { ThemeIcon, Tooltip } from \"@mantine/core\";\nimport { LuShieldCheck } from \"react-icons/lu\";\n\nexport const SecureInfo = () => {\n  return (\n    <Tooltip\n      label=\"Your data is processed locally on your device.\"\n      fz=\"xs\"\n      ta=\"center\"\n      maw=\"200\"\n      multiline\n      withArrow\n    >\n      <ThemeIcon\n        variant=\"light\"\n        color=\"teal\"\n        size=\"36\"\n        style={{\n          position: \"absolute\",\n          bottom: \"10px\",\n          right: \"10px\",\n          zIndex: 100,\n        }}\n        radius=\"xl\"\n      >\n        <LuShieldCheck size=\"22\" />\n      </ThemeIcon>\n    </Tooltip>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/ZoomControl.tsx",
    "content": "import React from \"react\";\nimport { ActionIcon, Flex, Tooltip, Text } from \"@mantine/core\";\nimport { useHotkeys } from \"@mantine/hooks\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { LuFocus, LuMaximize, LuMinus, LuPlus } from \"react-icons/lu\";\nimport { SearchInput } from \"../../Toolbar/SearchInput\";\nimport useGraph from \"./stores/useGraph\";\n\nexport const ZoomControl = () => {\n  const zoomIn = useGraph(state => state.zoomIn);\n  const zoomOut = useGraph(state => state.zoomOut);\n  const centerView = useGraph(state => state.centerView);\n  const focusFirstNode = useGraph(state => state.focusFirstNode);\n\n  useHotkeys(\n    [\n      [\"mod+[plus]\", zoomIn, { usePhysicalKeys: true }],\n      [\"mod+[minus]\", zoomOut, { usePhysicalKeys: true }],\n      [\"shift+Digit1\", focusFirstNode, { usePhysicalKeys: true }],\n      [\"shift+Digit2\", centerView, { usePhysicalKeys: true }],\n    ],\n    []\n  );\n\n  return (\n    <Flex\n      align=\"center\"\n      gap=\"xs\"\n      style={{\n        position: \"absolute\",\n        bottom: \"10px\",\n        left: \"10px\",\n        alignItems: \"start\",\n        zIndex: 100,\n      }}\n    >\n      <ActionIcon.Group borderWidth={0}>\n        <Tooltip\n          label={\n            <Flex fz=\"xs\" gap=\"md\">\n              <Text fz=\"xs\">Center first item</Text>\n              <Text fz=\"xs\" c=\"dimmed\">\n                ⇧ 1\n              </Text>\n            </Flex>\n          }\n          withArrow\n        >\n          <ActionIcon\n            size=\"lg\"\n            variant=\"light\"\n            color=\"gray\"\n            onClick={() => {\n              focusFirstNode();\n              gaEvent(\"focus_first_node\");\n            }}\n          >\n            <LuFocus />\n          </ActionIcon>\n        </Tooltip>\n        <Tooltip\n          label={\n            <Flex fz=\"xs\" gap=\"md\">\n              <Text fz=\"xs\">Fit to center</Text>\n              <Text fz=\"xs\" c=\"dimmed\">\n                ⇧ 2\n              </Text>\n            </Flex>\n          }\n          withArrow\n        >\n          <ActionIcon\n            size=\"lg\"\n            variant=\"light\"\n            color=\"gray\"\n            onClick={() => {\n              centerView();\n              gaEvent(\"center_view\");\n            }}\n          >\n            <LuMaximize />\n          </ActionIcon>\n        </Tooltip>\n        <ActionIcon\n          size=\"lg\"\n          variant=\"light\"\n          color=\"gray\"\n          onClick={() => {\n            zoomOut();\n            gaEvent(\"zoom_out\");\n          }}\n        >\n          <LuMinus />\n        </ActionIcon>\n        <ActionIcon\n          size=\"lg\"\n          variant=\"light\"\n          color=\"gray\"\n          onClick={() => {\n            zoomIn();\n            gaEvent(\"zoom_in\");\n          }}\n        >\n          <LuPlus />\n        </ActionIcon>\n      </ActionIcon.Group>\n      <SearchInput />\n    </Flex>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/index.tsx",
    "content": "import React from \"react\";\nimport { Box } from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport { JSONCrack } from \"jsoncrack-react\";\nimport type { NodeData } from \"jsoncrack-react\";\nimport { useLongPress } from \"use-long-press\";\nimport { SUPPORTED_LIMIT } from \"../../../../constants/graph\";\nimport useConfig from \"../../../../store/useConfig\";\nimport useJson from \"../../../../store/useJson\";\nimport { useModal } from \"../../../../store/useModal\";\nimport { NotSupported } from \"./NotSupported\";\nimport { OptionsMenu } from \"./OptionsMenu\";\nimport { SecureInfo } from \"./SecureInfo\";\nimport { ZoomControl } from \"./ZoomControl\";\nimport useGraph from \"./stores/useGraph\";\n\nconst StyledEditorWrapper = styled.div<{ $widget: boolean }>`\n  width: 100%;\n  height: 100%;\n\n  .jsoncrack-space,\n  .jsoncrack-space:active {\n    cursor: url(\"/assets/cursor.svg\"), auto;\n  }\n`;\n\ninterface GraphProps {\n  isWidget?: boolean;\n}\n\nexport const GraphView = ({ isWidget = false }: GraphProps) => {\n  const setViewPort = useGraph(state => state.setViewPort);\n  const direction = useGraph(state => state.direction);\n  const setSelectedNode = useGraph(state => state.setSelectedNode);\n  const gesturesEnabled = useConfig(state => state.gesturesEnabled);\n  const rulersEnabled = useConfig(state => state.rulersEnabled);\n  const darkmodeEnabled = useConfig(state => state.darkmodeEnabled);\n  const json = useJson(state => state.json);\n  const setVisible = useModal(state => state.setVisible);\n\n  const callback = React.useCallback(() => {\n    const canvas = document.querySelector(\".jsoncrack-canvas\") as HTMLDivElement | null;\n    canvas?.classList.add(\"dragging\");\n  }, []);\n\n  const bindLongPress = useLongPress(callback, {\n    threshold: 150,\n    onFinish: () => {\n      const canvas = document.querySelector(\".jsoncrack-canvas\") as HTMLDivElement | null;\n      canvas?.classList.remove(\"dragging\");\n    },\n  });\n\n  const blurOnClick = React.useCallback(() => {\n    if (\"activeElement\" in document) {\n      (document.activeElement as HTMLElement | null)?.blur();\n    }\n  }, []);\n\n  const handleNodeClick = React.useCallback(\n    (node: NodeData) => {\n      setSelectedNode(node);\n      setVisible(\"NodeModal\", true);\n    },\n    [setSelectedNode, setVisible]\n  );\n\n  const maxVisibleNodes = Number.isFinite(SUPPORTED_LIMIT) ? SUPPORTED_LIMIT : 1500;\n\n  return (\n    <Box pos=\"relative\" h=\"100%\" w=\"100%\">\n      {!isWidget && <OptionsMenu />}\n      {!isWidget && <SecureInfo />}\n      <ZoomControl />\n      <StyledEditorWrapper\n        $widget={isWidget}\n        onContextMenu={event => event.preventDefault()}\n        onClick={blurOnClick}\n        {...bindLongPress()}\n      >\n        <JSONCrack\n          key={[direction, gesturesEnabled, rulersEnabled].join(\"-\")}\n          json={json}\n          theme={darkmodeEnabled ? \"dark\" : \"light\"}\n          layoutDirection={direction}\n          showControls={false}\n          showGrid={rulersEnabled}\n          trackpadZoom={gesturesEnabled}\n          maxRenderableNodes={maxVisibleNodes}\n          centerOnLayout\n          onViewportCreate={setViewPort}\n          onNodeClick={handleNodeClick}\n          renderNodeLimitExceeded={() => <NotSupported />}\n        />\n      </StyledEditorWrapper>\n    </Box>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/lib/jsonParser.ts",
    "content": "/**\n * Copyright (c) JSON Crack\n * This source code is licensed under the Apache 2.0 license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport { parseTree, getNodePath, type Node } from \"jsonc-parser\";\nimport type { EdgeData, NodeData, NodeRow } from \"jsoncrack-react\";\nimport { calculateNodeSize } from \"./utils/calculateNodeSize\";\n\nexport type Graph = {\n  nodes: NodeData[];\n  edges: EdgeData[];\n};\n\nexport const parser = (json: string): Graph => {\n  const jsonTree = parseTree(json);\n  if (!jsonTree) return { nodes: [], edges: [] };\n\n  const nodes: NodeData[] = [];\n  const edges: EdgeData[] = [];\n  let nodeId = 1;\n  let edgeId = 1;\n\n  function traverse(node: Node, parentId?: string) {\n    const id = String(nodeId++);\n    const text: NodeRow[] = [];\n\n    // If parentId is provided, create an edge from parentId to the current node id\n    if (parentId !== undefined && node.parent?.type === \"array\") {\n      edges.push({\n        id: String(edgeId++),\n        from: parentId,\n        to: id,\n        text: \"\",\n      });\n    }\n\n    const isArray = node.type === \"array\";\n    const isRootArray = !node.parent || node.parent.type === \"array\";\n    if (isArray && isRootArray) {\n      const { width, height } = calculateNodeSize(`[${node.children?.length ?? \"0\"} items]`);\n      nodes.push({\n        id,\n        text: [\n          {\n            key: null,\n            value: `[${node.children?.length ?? 0} items]`,\n            type: \"array\",\n            childrenCount: node.children?.length,\n          },\n        ],\n        width,\n        height,\n        path: [],\n      });\n\n      node.children?.forEach(child => {\n        traverse(child, id);\n      });\n\n      return id;\n    }\n\n    node.children?.forEach(child => {\n      if (!child.children || !child.children[1]) return traverse(child, id);\n\n      const key = child.children[0].value ?? null;\n      const valueNode = child.children[1];\n      const type = valueNode.type;\n\n      if (type === \"array\") {\n        const targetIds: string[] = [];\n\n        valueNode.children?.forEach(arrayChild => {\n          const arrayChildId = traverse(arrayChild, undefined);\n          if (arrayChildId) targetIds.push(arrayChildId);\n        });\n\n        text.push({\n          key,\n          value: valueNode.value,\n          type,\n          to: targetIds.length > 0 ? targetIds : undefined,\n          childrenCount: valueNode.children?.length,\n        });\n\n        targetIds.forEach(targetId => {\n          edges.push({\n            id: String(edgeId++),\n            from: id,\n            to: targetId,\n            text: key ?? null,\n          });\n        });\n      } else if (type === \"object\") {\n        const objectNodeId = traverse(valueNode, id);\n        text.push({\n          key,\n          value: valueNode.value,\n          type,\n          childrenCount: Object.keys(valueNode.children ?? {}).length,\n          ...(objectNodeId && { to: [objectNodeId] }),\n        });\n\n        if (objectNodeId) {\n          edges.push({\n            id: String(edgeId++),\n            from: id,\n            to: objectNodeId,\n            text: key ?? null,\n          });\n        }\n      } else {\n        text.push({\n          key,\n          value: valueNode.value,\n          type,\n        });\n      }\n    });\n\n    // to handle case where empty object inside array [{}]\n    if (node.parent?.type === \"array\" && node.type === \"object\" && node.children?.length === 0) {\n      text.push({\n        key: null,\n        value: \"{0 keys}\",\n        type: \"object\",\n        childrenCount: 0,\n      });\n    }\n\n    const appendParentKey = () => {\n      const getParentKey = (targetNode: any) => {\n        const path = getNodePath(targetNode);\n        return path?.pop()?.toString();\n      };\n\n      if (!node.parent) {\n        return { parentKey: getParentKey(node), parentType: node.type };\n      }\n\n      if (node.parent.type === \"array\") {\n        return { parentKey: getParentKey(node.parent), parentType: \"array\" };\n      }\n\n      if (node.parent.type === \"property\") {\n        return { parentKey: getParentKey(node), parentType: \"object\" };\n      }\n\n      return {\n        parentKey: getParentKey(node),\n        parentType: node.parent.type.replace(\"property\", \"object\"),\n      };\n    };\n\n    // its for singular text like string, number, boolean, null\n    if (text.length === 0) {\n      if (typeof node.value === \"undefined\") return;\n      const { width, height } = calculateNodeSize(node.value);\n\n      nodes.push({\n        id,\n        text: [\n          {\n            key: null,\n            value: node.value,\n            type: node.type,\n          },\n        ],\n        width,\n        height,\n        path: getNodePath(node),\n        ...appendParentKey(),\n      });\n    } else {\n      let t: string | [string, string][];\n\n      if (text.some(t => t.key !== null)) {\n        t = text.map(t => {\n          const keyStr = t.key === null ? \"\" : t.key;\n          if (t.type === \"object\") return [keyStr, `{${t.childrenCount ?? 0} keys}`];\n          if (t.type === \"array\") return [keyStr, `[${t.childrenCount ?? 0} items]`];\n          if (t.value === null) return [keyStr, \"null\"];\n\n          return [keyStr, `${t.value}`];\n        });\n      } else {\n        t = `${text[0].value}`;\n      }\n\n      const { width, height } = calculateNodeSize(t);\n      nodes.push({\n        id,\n        text,\n        width,\n        height,\n        path: getNodePath(node),\n        ...appendParentKey(),\n      });\n    }\n\n    return id; // Return the current id for referencing in the parent node\n  }\n\n  traverse(jsonTree);\n  return { nodes, edges };\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/lib/utils/calculateNodeSize.ts",
    "content": "import { NODE_DIMENSIONS } from \"../../../../../../constants/graph\";\n\ntype Text = number | string | [string, string][];\ntype Size = { width: number; height: number };\n\nconst calculateLines = (text: Text): string => {\n  if (Array.isArray(text)) {\n    return text.map(([k, v]) => `${k}: ${JSON.stringify(v).slice(0, 80)}`).join(\"\\n\");\n  }\n\n  return `${text}`;\n};\n\nconst calculateWidthAndHeight = (str: string, single = false) => {\n  if (!str) return { width: 45, height: 45 };\n\n  const dummyElement = document.createElement(\"div\");\n  dummyElement.style.whiteSpace = single ? \"nowrap\" : \"pre-wrap\";\n  dummyElement.innerText = str;\n  dummyElement.style.fontSize = \"12px\";\n  dummyElement.style.width = \"fit-content\";\n  dummyElement.style.padding = \"0 10px\";\n  dummyElement.style.fontWeight = \"500\";\n  dummyElement.style.fontFamily = \"monospace\";\n  document.body.appendChild(dummyElement);\n\n  const clientRect = dummyElement.getBoundingClientRect();\n  const lines = str.split(\"\\n\").length;\n\n  const width = clientRect.width + 4;\n  // Use parent height for single line nodes that are parents\n  const height = single ? NODE_DIMENSIONS.PARENT_HEIGHT : lines * NODE_DIMENSIONS.ROW_HEIGHT;\n\n  document.body.removeChild(dummyElement);\n  return { width, height };\n};\n\nconst sizeCache = new Map<Text, Size>();\n\n// clear cache every 2 mins\nsetInterval(() => sizeCache.clear(), 120_000);\n\nexport const calculateNodeSize = (text: Text, isParent = false) => {\n  const cacheKey = [text, isParent].toString();\n\n  // check cache if data already exists\n  if (sizeCache.has(cacheKey)) {\n    const size = sizeCache.get(cacheKey);\n    if (size) return size;\n  }\n\n  const lines = calculateLines(text);\n  const sizes = calculateWidthAndHeight(lines, typeof text === \"string\");\n\n  if (isParent) sizes.width += 80;\n  if (sizes.width > 700) sizes.width = 700;\n\n  sizeCache.set(cacheKey, sizes);\n  return sizes;\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/lib/utils/getChildrenEdges.ts",
    "content": "import type { EdgeData, NodeData } from \"jsoncrack-react\";\n\nexport const getChildrenEdges = (nodes: NodeData[], edges: EdgeData[]): EdgeData[] => {\n  const nodeIds = nodes.map(node => node.id);\n\n  return edges.filter(\n    edge => nodeIds.includes(edge.from as string) || nodeIds.includes(edge.to as string)\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/lib/utils/getOutgoers.ts",
    "content": "import type { EdgeData, NodeData } from \"jsoncrack-react\";\n\ntype Outgoers = [NodeData[], string[]];\n\nexport const getOutgoers = (\n  nodeId: string,\n  nodes: NodeData[],\n  edges: EdgeData[],\n  parent: string[] = []\n): Outgoers => {\n  const outgoerNodes: NodeData[] = [];\n  const matchingNodes: string[] = [];\n\n  if (parent.includes(nodeId)) {\n    const initialParentNode = nodes.find(n => n.id === nodeId);\n\n    if (initialParentNode) outgoerNodes.push(initialParentNode);\n  }\n\n  const findOutgoers = (currentNodeId: string) => {\n    const outgoerIds = edges.filter(e => e.from === currentNodeId).map(e => e.to);\n    const nodeList = nodes.filter(n => {\n      if (parent.includes(n.id) && !matchingNodes.includes(n.id)) matchingNodes.push(n.id);\n      return outgoerIds.includes(n.id) && !parent.includes(n.id);\n    });\n\n    outgoerNodes.push(...nodeList);\n    nodeList.forEach(node => findOutgoers(node.id));\n  };\n\n  findOutgoers(nodeId);\n  return [outgoerNodes, matchingNodes];\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/stores/useGraph.ts",
    "content": "import type { LayoutDirection, NodeData } from \"jsoncrack-react\";\nimport type { ViewPort } from \"react-zoomable-ui\";\nimport { create } from \"zustand\";\n\nexport interface Graph {\n  viewPort: ViewPort | null;\n  direction: LayoutDirection;\n  fullscreen: boolean;\n  selectedNode: NodeData | null;\n}\n\nconst initialStates: Graph = {\n  viewPort: null,\n  direction: \"RIGHT\",\n  fullscreen: false,\n  selectedNode: null,\n};\n\ninterface GraphActions {\n  setDirection: (direction: LayoutDirection) => void;\n  setViewPort: (ref: ViewPort) => void;\n  setSelectedNode: (nodeData: NodeData | null) => void;\n  focusFirstNode: () => void;\n  toggleFullscreen: (value: boolean) => void;\n  zoomIn: () => void;\n  zoomOut: () => void;\n  centerView: () => void;\n}\n\nconst useGraph = create<Graph & GraphActions>((set, get) => ({\n  ...initialStates,\n  setSelectedNode: nodeData => set({ selectedNode: nodeData }),\n  setDirection: (direction = \"RIGHT\") => {\n    set({ direction });\n    setTimeout(() => get().centerView(), 200);\n  },\n  focusFirstNode: () => {\n    const rootNode = document.querySelector(\"g[id$='node-1']\");\n    get().viewPort?.camera?.centerFitElementIntoView(rootNode as HTMLElement, {\n      elementExtraMarginForZoom: 100,\n    });\n  },\n  zoomIn: () => {\n    const viewPort = get().viewPort;\n    viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor + 0.1);\n  },\n  zoomOut: () => {\n    const viewPort = get().viewPort;\n    viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor - 0.1);\n  },\n  centerView: () => {\n    const viewPort = get().viewPort;\n    viewPort?.updateContainerSize();\n\n    const canvas = document.querySelector(\".jsoncrack-canvas\") as HTMLElement | null;\n    if (canvas) {\n      viewPort?.camera?.centerFitElementIntoView(canvas);\n    }\n  },\n  toggleFullscreen: fullscreen => set({ fullscreen }),\n  setViewPort: viewPort => set({ viewPort }),\n}));\n\nexport default useGraph;\n"
  },
  {
    "path": "apps/www/src/features/editor/views/TreeView/Label.tsx",
    "content": "import React from \"react\";\nimport type { DefaultTheme } from \"styled-components\";\nimport { styled } from \"styled-components\";\nimport type { KeyPath } from \"react-json-tree\";\n\ninterface LabelProps {\n  keyPath: KeyPath;\n  nodeType: string;\n}\n\nfunction getLabelColor({ $type, theme }: { $type?: string; theme: DefaultTheme }) {\n  if ($type === \"Object\") return theme.NODE_COLORS.PARENT_OBJ;\n  if ($type === \"Array\") return theme.NODE_COLORS.PARENT_ARR;\n  return theme.NODE_COLORS.PARENT_OBJ;\n}\n\nconst StyledLabel = styled.span<{ $nodeType?: string }>`\n  color: ${({ theme, $nodeType }) => getLabelColor({ theme, $type: $nodeType })};\n\n  &:hover {\n    filter: brightness(1.5);\n    transition: filter 0.2s ease-in-out;\n  }\n`;\n\nexport const Label = ({ keyPath, nodeType }: LabelProps) => {\n  return <StyledLabel $nodeType={nodeType}>{keyPath[0]}:</StyledLabel>;\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/TreeView/Value.tsx",
    "content": "import React from \"react\";\nimport type { DefaultTheme } from \"styled-components\";\nimport { useTheme } from \"styled-components\";\nimport { TextRenderer } from \"../GraphView/CustomNode/TextRenderer\";\n\ntype TextColorFn = {\n  theme: DefaultTheme;\n  $value?: string | unknown;\n};\n\nfunction getValueColor({ $value, theme }: TextColorFn) {\n  if ($value && !Number.isNaN(+$value)) return theme.NODE_COLORS.INTEGER;\n  if ($value === \"true\") return theme.NODE_COLORS.BOOL.TRUE;\n  if ($value === \"false\") return theme.NODE_COLORS.BOOL.FALSE;\n  if ($value === \"null\") return theme.NODE_COLORS.NULL;\n\n  // default\n  return theme.NODE_COLORS.NODE_VALUE;\n}\n\ninterface ValueProps {\n  valueAsString: unknown;\n  value: unknown;\n}\n\nexport const Value = (props: ValueProps) => {\n  const theme = useTheme();\n  const { valueAsString, value } = props;\n\n  return (\n    <span\n      style={{\n        color: getValueColor({\n          theme,\n          $value: valueAsString,\n        }),\n      }}\n    >\n      <TextRenderer>{JSON.stringify(value)}</TextRenderer>\n    </span>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/editor/views/TreeView/index.tsx",
    "content": "import React from \"react\";\nimport { useTheme } from \"styled-components\";\nimport { JSONTree } from \"react-json-tree\";\nimport useJson from \"../../../../store/useJson\";\nimport { Label } from \"./Label\";\nimport { Value } from \"./Value\";\n\nexport const TreeView = () => {\n  const theme = useTheme();\n  const json = useJson(state => state.json);\n\n  return (\n    <JSONTree\n      hideRoot\n      data={JSON.parse(json)}\n      valueRenderer={(valueAsString, value) => <Value {...{ valueAsString, value }} />}\n      labelRenderer={(keyPath, nodeType) => <Label {...{ keyPath, nodeType }} />}\n      theme={{\n        extend: {\n          overflow: \"scroll\",\n          height: \"100%\",\n          scheme: \"monokai\",\n          author: \"wimer hazenberg (http://www.monokai.nl)\",\n          base00: theme.GRID_BG_COLOR,\n        },\n      }}\n    />\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/modals/DownloadModal/index.tsx",
    "content": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport {\n  ColorPicker,\n  TextInput,\n  SegmentedControl,\n  Group,\n  Modal,\n  Button,\n  Divider,\n  ColorInput,\n} from \"@mantine/core\";\nimport { toBlob, toJpeg, toPng, toSvg } from \"html-to-image\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport toast from \"react-hot-toast\";\nimport { FiCopy, FiDownload } from \"react-icons/fi\";\n\nenum Extensions {\n  SVG = \"svg\",\n  PNG = \"png\",\n  JPEG = \"jpeg\",\n}\n\nconst getDownloadFormat = (format: Extensions) => {\n  switch (format) {\n    case Extensions.SVG:\n      return toSvg;\n    case Extensions.PNG:\n      return toPng;\n    case Extensions.JPEG:\n      return toJpeg;\n  }\n};\n\nconst swatches = [\n  \"#B80000\",\n  \"#DB3E00\",\n  \"#FCCB00\",\n  \"#008B02\",\n  \"#006B76\",\n  \"#1273DE\",\n  \"#004DCF\",\n  \"#5300EB\",\n  \"#EB9694\",\n  \"#FAD0C3\",\n  \"#FEF3BD\",\n  \"#C1E1C5\",\n  \"#BEDADC\",\n  \"#C4DEF6\",\n  \"#BED3F3\",\n  \"#D4C4FB\",\n  \"transparent\",\n];\n\nfunction downloadURI(uri: string, name: string) {\n  const link = document.createElement(\"a\");\n\n  link.download = name;\n  link.href = uri;\n  document.body.appendChild(link);\n  link.click();\n  document.body.removeChild(link);\n}\n\nconst getExportElement = () =>\n  (document.querySelector(\".jsoncrack-canvas\") as HTMLElement | null) ??\n  (document.querySelector(\"svg[id*='ref']\") as HTMLElement | null);\n\nexport const DownloadModal = ({ opened, onClose }: ModalProps) => {\n  const [extension, setExtension] = React.useState(Extensions.PNG);\n  const [fileDetails, setFileDetails] = React.useState({\n    filename: \"jsoncrack.com\",\n    backgroundColor: \"#FFFFFF\",\n    quality: 1,\n  });\n\n  const clipboardImage = async () => {\n    try {\n      toast.loading(\"Copying to clipboard...\", { id: \"toastClipboard\" });\n\n      const imageElement = getExportElement();\n      if (!imageElement) {\n        toast.error(\"Canvas not found.\");\n        return;\n      }\n      const imageOptions = {\n        quality: fileDetails.quality,\n        backgroundColor: fileDetails.backgroundColor,\n        skipFonts: true,\n      };\n\n      const blob = await toBlob(imageElement, imageOptions);\n\n      if (!blob) return;\n\n      await navigator.clipboard?.write([\n        new ClipboardItem({\n          [blob.type]: blob,\n        }),\n      ]);\n\n      toast.success(\"Copied to clipboard\");\n      gaEvent(\"clipboard_img\");\n    } catch (error) {\n      if (error instanceof Error && error.name === \"NotAllowedError\") {\n        toast.error(\n          \"Clipboard write permission denied. Please allow clipboard access in your browser settings.\"\n        );\n      } else {\n        toast.error(\"Failed to copy to clipboard\");\n      }\n    } finally {\n      toast.dismiss(\"toastClipboard\");\n      onClose();\n    }\n  };\n\n  const exportAsImage = async () => {\n    try {\n      toast.loading(\"Downloading...\", { id: \"toastDownload\" });\n\n      const imageElement = getExportElement();\n      if (!imageElement) {\n        toast.error(\"Canvas not found.\");\n        return;\n      }\n      const imageOptions = {\n        quality: fileDetails.quality,\n        backgroundColor: fileDetails.backgroundColor,\n        skipFonts: true,\n      };\n\n      const dataURI = await getDownloadFormat(extension)(imageElement, imageOptions);\n\n      downloadURI(dataURI, `${fileDetails.filename}.${extension}`);\n      gaEvent(\"download_img\", { label: extension });\n    } catch {\n      toast.error(\"Failed to download image!\");\n    } finally {\n      toast.dismiss(\"toastDownload\");\n      onClose();\n    }\n  };\n\n  const updateDetails = (key: keyof typeof fileDetails, value: string | number) =>\n    setFileDetails({ ...fileDetails, [key]: value });\n\n  return (\n    <Modal opened={opened} onClose={onClose} title=\"Download Image\" centered>\n      <TextInput\n        label=\"File Name\"\n        value={fileDetails.filename}\n        onChange={e => updateDetails(\"filename\", e.target.value)}\n        mb=\"lg\"\n      />\n      <SegmentedControl\n        value={extension}\n        onChange={e => setExtension(e as Extensions)}\n        fullWidth\n        data={[\n          { label: \"PNG\", value: Extensions.PNG },\n          { label: \"JPEG\", value: Extensions.JPEG },\n          { label: \"SVG\", value: Extensions.SVG },\n        ]}\n        mb=\"lg\"\n      />\n      <ColorInput\n        label=\"Background Color\"\n        value={fileDetails.backgroundColor}\n        onChange={color => updateDetails(\"backgroundColor\", color)}\n        withEyeDropper={false}\n        mb=\"lg\"\n      />\n      <ColorPicker\n        format=\"rgba\"\n        value={fileDetails.backgroundColor}\n        onChange={color => updateDetails(\"backgroundColor\", color)}\n        swatches={swatches}\n        withPicker={false}\n        fullWidth\n      />\n      <Divider my=\"xs\" />\n      <Group justify=\"right\">\n        <Button leftSection={<FiCopy />} onClick={clipboardImage}>\n          Clipboard\n        </Button>\n        <Button color=\"green\" leftSection={<FiDownload />} onClick={exportAsImage}>\n          Download\n        </Button>\n      </Group>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/modals/ImportModal/index.tsx",
    "content": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Modal, Group, Button, TextInput, Stack, Paper, Text } from \"@mantine/core\";\nimport { Dropzone } from \"@mantine/dropzone\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport toast from \"react-hot-toast\";\nimport { AiOutlineUpload } from \"react-icons/ai\";\nimport type { FileFormat } from \"../../../enums/file.enum\";\nimport useFile from \"../../../store/useFile\";\n\nexport const ImportModal = ({ opened, onClose }: ModalProps) => {\n  const [url, setURL] = React.useState(\"\");\n  const [file, setFile] = React.useState<File | null>(null);\n\n  const setContents = useFile(state => state.setContents);\n  const setFormat = useFile(state => state.setFormat);\n\n  const handleImportFile = () => {\n    if (url) {\n      setFile(null);\n\n      toast.loading(\"Loading...\", { id: \"toastFetch\" });\n      gaEvent(\"fetch_url\");\n\n      return fetch(url)\n        .then(res => res.json())\n        .then(json => {\n          setContents({ contents: JSON.stringify(json, null, 2) });\n          onClose();\n        })\n        .catch(() => toast.error(\"Failed to fetch JSON!\"))\n        .finally(() => toast.dismiss(\"toastFetch\"));\n    } else if (file) {\n      const lastIndex = file.name.lastIndexOf(\".\");\n      const format = file.name.substring(lastIndex + 1);\n      setFormat(format as FileFormat);\n\n      file.text().then(text => {\n        setContents({ contents: text });\n        setFile(null);\n        setURL(\"\");\n        onClose();\n      });\n\n      gaEvent(\"import_file\", { label: format });\n    }\n  };\n\n  return (\n    <Modal\n      title=\"Import File\"\n      opened={opened}\n      onClose={() => {\n        setFile(null);\n        setURL(\"\");\n        onClose();\n      }}\n      centered\n    >\n      <Stack py=\"sm\">\n        <TextInput\n          value={url}\n          onChange={e => setURL(e.target.value)}\n          type=\"url\"\n          placeholder=\"URL of JSON to fetch\"\n          data-autofocus\n        />\n        <Paper radius=\"md\" style={{ cursor: \"pointer\" }}>\n          <Dropzone\n            onDrop={files => setFile(files[0])}\n            onReject={files => toast.error(`Unable to load file ${files[0].file.name}`)}\n            maxFiles={1}\n            p=\"md\"\n            accept={[\"application/json\", \"application/x-yaml\", \"text/csv\", \"application/xml\"]}\n          >\n            <Stack justify=\"center\" align=\"center\" gap=\"sm\" mih={220}>\n              <AiOutlineUpload size={48} />\n              <Text fw=\"bold\">Drop here or click to upload files</Text>\n              <Text c=\"dimmed\" fz=\"sm\">\n                {file?.name ?? \"None\"}\n              </Text>\n            </Stack>\n          </Dropzone>\n        </Paper>\n      </Stack>\n      <Group justify=\"right\">\n        <Button onClick={handleImportFile} disabled={!(file || url)}>\n          Import\n        </Button>\n      </Group>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/modals/JPathModal/index.tsx",
    "content": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Stack, Modal, Button, Text, Anchor, Group, TextInput } from \"@mantine/core\";\nimport { JSONPath } from \"jsonpath-plus\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport toast from \"react-hot-toast\";\nimport { VscLinkExternal } from \"react-icons/vsc\";\nimport useFile from \"../../../store/useFile\";\nimport useJson from \"../../../store/useJson\";\n\nexport const JPathModal = ({ opened, onClose }: ModalProps) => {\n  const getJson = useJson(state => state.getJson);\n  const setContents = useFile(state => state.setContents);\n  const [query, setQuery] = React.useState(\"\");\n\n  const evaluteJsonPath = () => {\n    try {\n      const json = getJson();\n      const result = JSONPath({ path: query, json: JSON.parse(json) });\n\n      setContents({ contents: JSON.stringify(result, null, 2) });\n      gaEvent(\"run_json_path\");\n      onClose();\n    } catch (error) {\n      if (error instanceof Error) toast.error(error.message);\n    }\n  };\n\n  return (\n    <Modal title=\"JSON Path\" size=\"lg\" opened={opened} onClose={onClose} centered>\n      <Stack>\n        <Text fz=\"sm\">\n          JsonPath expressions always refer to a JSON structure in the same way as XPath expression\n          are used in combination with an XML document. The &quot;root member object&quot; in\n          JsonPath is always referred to as $ regardless if it is an object or array.\n          <br />\n          <Anchor\n            fz=\"sm\"\n            target=\"_blank\"\n            href=\"https://docs.oracle.com/cd/E60058_01/PDF/8.0.8.x/8.0.8.0.0/PMF_HTML/JsonPath_Expressions.htm\"\n            rel=\"noopener noreferrer\"\n          >\n            Read documentation. <VscLinkExternal />\n          </Anchor>\n        </Text>\n        <TextInput\n          value={query}\n          onChange={e => setQuery(e.currentTarget.value)}\n          placeholder=\"Enter JSON Path...\"\n          data-autofocus\n        />\n        <Group justify=\"right\">\n          <Button onClick={evaluteJsonPath} disabled={!query.length}>\n            Run\n          </Button>\n        </Group>\n      </Stack>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/modals/JQModal/index.tsx",
    "content": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Stack, Modal, Button, Text, Anchor, Group, TextInput } from \"@mantine/core\";\nimport { VscLinkExternal } from \"react-icons/vsc\";\nimport useJsonQuery from \"../../../hooks/useJsonQuery\";\n\nexport const JQModal = ({ opened, onClose }: ModalProps) => {\n  const { updateJson } = useJsonQuery();\n  const [query, setQuery] = React.useState(\"\");\n\n  return (\n    <Modal title=\"JSON Query\" size=\"lg\" opened={opened} onClose={onClose} centered>\n      <Stack>\n        <Text fz=\"sm\">\n          jq is a lightweight and flexible command-line JSON processor. JSON Crack uses simplified\n          version of jq, not all features are supported.\n          <br />\n          <Anchor\n            fz=\"sm\"\n            target=\"_blank\"\n            href=\"https://jqlang.github.io/jq/manual/\"\n            rel=\"noopener noreferrer\"\n          >\n            Read documentation. <VscLinkExternal />\n          </Anchor>\n        </Text>\n        <TextInput\n          leftSection=\"jq\"\n          placeholder=\"Enter jq query\"\n          value={query}\n          onChange={e => setQuery(e.currentTarget.value)}\n        />\n        <Group justify=\"right\">\n          <Button onClick={() => updateJson(query, onClose)}>Display on Graph</Button>\n        </Group>\n      </Stack>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/modals/ModalController.tsx",
    "content": "import React from \"react\";\nimport * as ModalComponents from \".\";\nimport { useModal } from \"../../store/useModal\";\nimport { modals, type ModalName } from \"./modalTypes\";\n\nconst Modal = ({ modalKey }: { modalKey: ModalName }) => {\n  const opened = useModal(state => state[modalKey]);\n  const setVisible = useModal(state => state.setVisible);\n  const ModalComponent = ModalComponents[modalKey];\n\n  return <ModalComponent opened={opened} onClose={() => setVisible(modalKey, false)} />;\n};\n\nconst ModalController = () => {\n  return modals.map(modal => <Modal key={modal} modalKey={modal} />);\n};\n\nexport default ModalController;\n"
  },
  {
    "path": "apps/www/src/features/modals/NodeModal/index.tsx",
    "content": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Modal, Stack, Text, ScrollArea, Flex, CloseButton } from \"@mantine/core\";\nimport { CodeHighlight } from \"@mantine/code-highlight\";\nimport type { NodeData } from \"jsoncrack-react\";\nimport useGraph from \"../../editor/views/GraphView/stores/useGraph\";\n\n// return object from json removing array and object fields\nconst normalizeNodeData = (nodeRows: NodeData[\"text\"]) => {\n  if (!nodeRows || nodeRows.length === 0) return \"{}\";\n  if (nodeRows.length === 1 && !nodeRows[0].key) return `${nodeRows[0].value}`;\n\n  const obj = {};\n  nodeRows?.forEach(row => {\n    if (row.type !== \"array\" && row.type !== \"object\") {\n      if (row.key) obj[row.key] = row.value;\n    }\n  });\n  return JSON.stringify(obj, null, 2);\n};\n\n// return json path in the format $[\"customer\"]\nconst jsonPathToString = (path?: NodeData[\"path\"]) => {\n  if (!path || path.length === 0) return \"$\";\n  const segments = path.map(seg => (typeof seg === \"number\" ? seg : `\"${seg}\"`));\n  return `$[${segments.join(\"][\")}]`;\n};\n\nexport const NodeModal = ({ opened, onClose }: ModalProps) => {\n  const nodeData = useGraph(state => state.selectedNode);\n\n  return (\n    <Modal size=\"auto\" opened={opened} onClose={onClose} centered withCloseButton={false}>\n      <Stack pb=\"sm\" gap=\"sm\">\n        <Stack gap=\"xs\">\n          <Flex justify=\"space-between\" align=\"center\">\n            <Text fz=\"xs\" fw={500}>\n              Content\n            </Text>\n            <CloseButton onClick={onClose} />\n          </Flex>\n          <ScrollArea.Autosize mah={250} maw={600}>\n            <CodeHighlight\n              code={normalizeNodeData(nodeData?.text ?? [])}\n              miw={350}\n              maw={600}\n              language=\"json\"\n              withCopyButton\n            />\n          </ScrollArea.Autosize>\n        </Stack>\n        <Text fz=\"xs\" fw={500}>\n          JSON Path\n        </Text>\n        <ScrollArea.Autosize maw={600}>\n          <CodeHighlight\n            code={jsonPathToString(nodeData?.path)}\n            miw={350}\n            mah={250}\n            language=\"json\"\n            copyLabel=\"Copy to clipboard\"\n            copiedLabel=\"Copied to clipboard\"\n            withCopyButton\n          />\n        </ScrollArea.Autosize>\n      </Stack>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/modals/SchemaModal/index.tsx",
    "content": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Stack, Modal, Button, Text, Anchor, Group, Paper } from \"@mantine/core\";\nimport Editor from \"@monaco-editor/react\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { toast } from \"react-hot-toast\";\nimport { VscLinkExternal } from \"react-icons/vsc\";\nimport useConfig from \"../../../store/useConfig\";\nimport useFile from \"../../../store/useFile\";\n\nexport const SchemaModal = ({ opened, onClose }: ModalProps) => {\n  const setJsonSchema = useFile(state => state.setJsonSchema);\n  const darkmodeEnabled = useConfig(state => (state.darkmodeEnabled ? \"vs-dark\" : \"light\"));\n  const [schema, setSchema] = React.useState(\n    JSON.stringify(\n      {\n        $schema: \"http://json-schema.org/draft-04/schema#\",\n        title: \"Product\",\n        description: \"A product from catalog\",\n        type: \"object\",\n        properties: {\n          id: {\n            description: \"The unique identifier for a product\",\n            type: \"integer\",\n          },\n        },\n        required: [\"id\"],\n      },\n      null,\n      2\n    )\n  );\n\n  const onApply = () => {\n    try {\n      const parsedSchema = JSON.parse(schema);\n      setJsonSchema(parsedSchema);\n\n      gaEvent(\"apply_json_schema\");\n      toast.success(\"Applied schema!\");\n      onClose();\n    } catch {\n      toast.error(\"Invalid Schema\");\n    }\n  };\n\n  const onClear = () => {\n    setJsonSchema(null);\n    setSchema(\"\");\n    toast(\"Disabled JSON Schema\");\n    onClose();\n  };\n\n  return (\n    <Modal title=\"JSON Schema\" size=\"lg\" opened={opened} onClose={onClose} centered>\n      <Stack>\n        <Text fz=\"sm\">Any validation failures are shown at the bottom toolbar of pane.</Text>\n        <Anchor\n          fz=\"sm\"\n          target=\"_blank\"\n          href=\"https://niem.github.io/json/sample-schema/\"\n          rel=\"noopener noreferrer\"\n        >\n          View Examples <VscLinkExternal />\n        </Anchor>\n        <Paper withBorder radius=\"sm\" style={{ overflow: \"hidden\" }}>\n          <Editor\n            value={schema ?? \"\"}\n            theme={darkmodeEnabled}\n            onChange={e => setSchema(e!)}\n            height={300}\n            language=\"json\"\n            options={{\n              formatOnPaste: true,\n              tabSize: 2,\n              formatOnType: true,\n              scrollBeyondLastLine: false,\n              stickyScroll: { enabled: false },\n              minimap: { enabled: false },\n            }}\n          />\n        </Paper>\n        <Group p=\"0\" justify=\"right\">\n          <Button variant=\"subtle\" color=\"gray\" onClick={onClear} disabled={!schema}>\n            Clear\n          </Button>\n          <Button variant=\"default\" onClick={onApply} disabled={!schema}>\n            Apply\n          </Button>\n        </Group>\n      </Stack>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/modals/TypeModal/index.tsx",
    "content": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Stack, Modal, Select, ScrollArea } from \"@mantine/core\";\nimport { CodeHighlight } from \"@mantine/code-highlight\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport useJson from \"../../../store/useJson\";\n\nenum Language {\n  TypeScript = \"typescript\",\n  TypeScript_Combined = \"typescript/typealias\",\n  Go = \"go\",\n  JSON_SCHEMA = \"json_schema\",\n  Kotlin = \"kotlin\",\n  Rust = \"rust\",\n}\n\nconst typeOptions = [\n  {\n    label: \"TypeScript\",\n    value: Language.TypeScript,\n    lang: \"typescript\",\n  },\n  {\n    label: \"TypeScript (combined)\",\n    value: Language.TypeScript_Combined,\n    lang: \"typescript\",\n  },\n  {\n    label: \"Go\",\n    value: Language.Go,\n    lang: \"go\",\n  },\n  {\n    label: \"JSON Schema\",\n    value: Language.JSON_SCHEMA,\n    lang: \"json\",\n  },\n  {\n    label: \"Kotlin\",\n    value: Language.Kotlin,\n    lang: \"kotlin\",\n  },\n  {\n    label: \"Rust\",\n    value: Language.Rust,\n    lang: \"rust\",\n  },\n];\n\nexport const TypeModal = ({ opened, onClose }: ModalProps) => {\n  const getJson = useJson(state => state.getJson);\n  const [type, setType] = React.useState(\"\");\n  const [selectedType, setSelectedType] = React.useState<Language>(Language.TypeScript);\n\n  const editorLanguage = React.useMemo(() => {\n    return typeOptions[typeOptions.findIndex(o => o.value === selectedType)]?.lang;\n  }, [selectedType]);\n\n  const transformer = React.useCallback(\n    async ({ value }) => {\n      const { run } = await import(\"json_typegen_wasm\");\n      return run(\n        \"Root\",\n        value,\n        JSON.stringify({\n          output_mode: selectedType,\n        })\n      );\n    },\n    [selectedType]\n  );\n\n  React.useEffect(() => {\n    if (opened) {\n      try {\n        if (selectedType === Language.Go) {\n          import(\"../../../lib/utils/json2go\").then(jtg => {\n            import(\"gofmt.js\").then(gofmt => {\n              const types = jtg.default(getJson());\n              setType(gofmt.default(types.go));\n            });\n          });\n        } else {\n          transformer({ value: getJson() }).then(setType);\n        }\n      } catch (error) {\n        console.error(error);\n      }\n    }\n  }, [getJson, opened, selectedType, transformer]);\n\n  return (\n    <Modal title=\"Generate Types\" size=\"lg\" opened={opened} onClose={onClose} centered>\n      <Stack pos=\"relative\">\n        <Select\n          value={selectedType}\n          data={typeOptions}\n          onChange={e => {\n            setSelectedType(e as Language);\n            gaEvent(\"generate_type\", { label: e as Language });\n          }}\n          allowDeselect={false}\n        />\n        <ScrollArea.Autosize mah={400} maw={700}>\n          <CodeHighlight\n            language={editorLanguage}\n            copyLabel=\"Copy to clipboard\"\n            copiedLabel=\"Copied to clipboard\"\n            radius={6}\n            code={type}\n          />\n        </ScrollArea.Autosize>\n      </Stack>\n    </Modal>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/features/modals/index.ts",
    "content": "export { DownloadModal } from \"./DownloadModal\";\nexport { ImportModal } from \"./ImportModal\";\nexport { NodeModal } from \"./NodeModal\";\nexport { SchemaModal } from \"./SchemaModal\";\nexport { JQModal } from \"./JQModal\";\nexport { TypeModal } from \"./TypeModal\";\nexport { JPathModal } from \"./JPathModal\";\n"
  },
  {
    "path": "apps/www/src/features/modals/modalTypes.ts",
    "content": "import * as ModalComponents from \".\";\n\n// Define the modals array separate from the component logic\nexport const modals = Object.freeze(Object.keys(ModalComponents)) as Extract<\n  keyof typeof ModalComponents,\n  string\n>[];\n\nexport type ModalName = (typeof modals)[number];\n"
  },
  {
    "path": "apps/www/src/hooks/useFocusNode.ts",
    "content": "import React from \"react\";\nimport { useDebouncedValue } from \"@mantine/hooks\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport useGraph from \"../features/editor/views/GraphView/stores/useGraph\";\nimport { cleanupHighlight, searchQuery, highlightMatchedNodes } from \"../lib/utils/search\";\n\nexport const useFocusNode = () => {\n  const viewPort = useGraph(state => state.viewPort);\n  const [selectedNode, setSelectedNode] = React.useState(0);\n  const [nodeCount, setNodeCount] = React.useState(0);\n  const [value, setValue] = React.useState(\"\");\n  const [debouncedValue] = useDebouncedValue(value, 600);\n\n  const skip = () => setSelectedNode(current => (current + 1) % nodeCount);\n\n  React.useEffect(() => {\n    if (!value) {\n      cleanupHighlight();\n      setSelectedNode(0);\n      setNodeCount(0);\n      return;\n    }\n\n    if (!viewPort || !debouncedValue) return;\n    const matchedNodes: NodeListOf<Element> = searchQuery(`span[data-key*='${debouncedValue}' i]`);\n    const matchedNode: Element | null = matchedNodes[selectedNode] || null;\n\n    cleanupHighlight();\n\n    if (matchedNode && matchedNode.parentElement) {\n      highlightMatchedNodes(matchedNodes, selectedNode);\n      setNodeCount(matchedNodes.length);\n\n      viewPort?.camera.centerFitElementIntoView(matchedNode.parentElement, {\n        elementExtraMarginForZoom: 400,\n      });\n    } else {\n      setSelectedNode(0);\n      setNodeCount(0);\n    }\n\n    gaEvent(\"search_graph\");\n  }, [selectedNode, debouncedValue, value, viewPort]);\n\n  return [value, setValue, skip, nodeCount, selectedNode] as const;\n};\n"
  },
  {
    "path": "apps/www/src/hooks/useJsonQuery.ts",
    "content": "import toast from \"react-hot-toast\";\nimport useFile from \"../store/useFile\";\nimport useJson from \"../store/useJson\";\n\nconst useJsonQuery = () => {\n  const getJson = useJson(state => state.getJson);\n  const setContents = useFile(state => state.setContents);\n\n  const transformer = async ({ value }) => {\n    const { run } = await import(\"json_typegen_wasm\");\n    return run(\"Root\", value, JSON.stringify({ output_mode: \"typescript/typealias\" }));\n  };\n\n  const updateJson = async (query: string, cb?: () => void) => {\n    try {\n      const jq = await import(\"jq-web\");\n      const res = await jq.promised.json(JSON.parse(getJson()), query);\n\n      setContents({ contents: JSON.stringify(res, null, 2) });\n      cb?.();\n    } catch (error) {\n      console.error(error);\n      toast.error(\"Unable to process the request.\");\n    }\n  };\n\n  const getJsonType = async () => {\n    const types = await transformer({ value: getJson() });\n    return types;\n  };\n\n  return { updateJson, getJsonType };\n};\n\nexport default useJsonQuery;\n"
  },
  {
    "path": "apps/www/src/layout/ConverterLayout/PageLinks.tsx",
    "content": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Anchor, Button, Flex, List, SimpleGrid, Stack } from \"@mantine/core\";\nimport { FaArrowRightLong } from \"react-icons/fa6\";\nimport { formats } from \"../../enums/file.enum\";\n\nconst languages = formats.map(format => format.label);\n\nfunction groupCombinations(array: string[]): Record<string, string[]> {\n  // Create an object to hold the grouped combinations\n  const grouped = {};\n\n  // Iterate over each item in the array\n  array.forEach(from => {\n    // Filter out the same item for the \"to\" array\n    const targets = array.filter(to => to !== from);\n\n    // Add the \"from\" item as the key and the \"to\" items as the value array\n    grouped[from] = targets;\n  });\n\n  return grouped;\n}\n\nconst groupedLanguages = groupCombinations(languages);\n\nexport const PageLinks = () => {\n  return (\n    <Flex justify=\"space-between\" align=\"center\">\n      <Stack gap=\"sm\" py=\"md\" justify=\"center\">\n        <Button\n          component={Link}\n          prefetch={false}\n          href=\"/editor\"\n          radius=\"md\"\n          size=\"sm\"\n          color=\"dark.5\"\n          autoContrast\n          w=\"fit-content\"\n          rightSection={<FaArrowRightLong />}\n          style={{\n            boxShadow: \"rgba(0, 0, 0, 0.12) 0px -3px 0px 0px inset\",\n            border: \"none\",\n          }}\n        >\n          Open JSON Crack\n        </Button>\n      </Stack>\n      <SimpleGrid cols={4} w=\"fit-content\">\n        {Object.entries(groupedLanguages).map(([from, tos]) => (\n          <List key={from} listStyleType=\"none\">\n            {tos.map(to => (\n              <List.Item key={to} c=\"black\">\n                <Anchor\n                  component={Link}\n                  prefetch={false}\n                  c=\"black\"\n                  href={`/converter/${from.toLowerCase()}-to-${to.toLowerCase()}`}\n                >\n                  {from} to {to}\n                </Anchor>\n              </List.Item>\n            ))}\n          </List>\n        ))}\n      </SimpleGrid>\n    </Flex>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/ConverterLayout/ToolPage.tsx",
    "content": "import React, { useEffect, useRef } from \"react\";\nimport Head from \"next/head\";\nimport { Box, Container, Flex, Paper, Text, Title } from \"@mantine/core\";\nimport { Editor } from \"@monaco-editor/react\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { LuCheck, LuCircleX } from \"react-icons/lu\";\nimport { SEO } from \"../../constants/seo\";\nimport { type FileFormat, formats } from \"../../enums/file.enum\";\nimport { contentToJson, jsonToContent } from \"../../lib/utils/jsonAdapter\";\nimport Layout from \"../PageLayout\";\nimport { PageLinks } from \"./PageLinks\";\nimport { editorOptions } from \"./options\";\n\ninterface ToolPageProps {\n  from: FileFormat;\n  to: FileFormat;\n}\n\nexport const ToolPage = ({ from, to }: ToolPageProps) => {\n  const editorRef = useRef<any>(null);\n  const [contentHasError, setContentHasError] = React.useState(false);\n  const [originalContent, setOriginalContent] = React.useState(\"\");\n  const [convertedContent, setConvertedContent] = React.useState(\"\");\n  const [scrollPosition, setScrollPosition] = React.useState(0);\n  const [editorHeight, setEditorHeight] = React.useState(0);\n\n  const fromLabel = formats.find(({ value }) => value === from)?.label;\n  const toLabel = formats.find(({ value }) => value === to)?.label;\n\n  useEffect(() => {\n    if (!originalContent.length) return;\n\n    (async () => {\n      try {\n        const json = await contentToJson(originalContent, from);\n        const content = await jsonToContent(JSON.stringify(json), to);\n        setConvertedContent(content);\n        setContentHasError(false);\n      } catch {\n        setContentHasError(true);\n        setConvertedContent(\"\");\n      }\n    })();\n  }, [from, originalContent, to]);\n\n  useEffect(() => {\n    const scrollPositionRatio =\n      (scrollPosition / editorHeight) * (editorRef.current?.getContentHeight() || 0);\n\n    editorRef.current?.setScrollTop(scrollPositionRatio);\n  }, [editorHeight, scrollPosition]);\n\n  return (\n    <Layout>\n      <Head>\n        {generateNextSeo({\n          ...SEO,\n          title: `${fromLabel} to ${toLabel} | JSON Crack`,\n          canonical: `https://jsoncrack.com/converter/${from}-to-${to}`,\n          description: `Convert ${fromLabel} to ${toLabel} using this free online tool. Upload your ${fromLabel} file and get the converted ${fromLabel} file instantly.`,\n        })}\n      </Head>\n      <Container mt=\"xl\" size=\"lg\">\n        <Title c=\"black\">\n          {fromLabel} to {toLabel} Converter\n        </Title>\n        <PageLinks />\n        <Flex pt=\"lg\" gap=\"40\">\n          <Paper mah=\"600px\" withBorder flex=\"1\" style={{ overflow: \"hidden\" }}>\n            <Box p=\"xs\" bg=\"gray\">\n              <Flex justify=\"space-between\" align=\"center\">\n                <Text c=\"gray.3\">{fromLabel}</Text>\n                {contentHasError && !!originalContent ? (\n                  <LuCircleX color=\"red\" />\n                ) : (\n                  <LuCheck color=\"lightgreen\" />\n                )}\n              </Flex>\n            </Box>\n            <Editor\n              value={originalContent}\n              onChange={value => setOriginalContent(value || \"\")}\n              language={from}\n              height={500}\n              options={editorOptions}\n              onMount={editor => {\n                editor.onDidContentSizeChange(() => {\n                  setEditorHeight(editor.getContentHeight());\n                });\n\n                editor.onDidScrollChange(e => {\n                  setScrollPosition(e.scrollTop);\n                });\n              }}\n            />\n          </Paper>\n          <Paper mah=\"600px\" withBorder flex=\"1\" style={{ overflow: \"hidden\" }}>\n            <Box p=\"xs\" bg=\"gray\">\n              <Text c=\"gray.3\">{toLabel}</Text>\n            </Box>\n            <Editor\n              value={convertedContent}\n              language={to}\n              height={500}\n              options={{\n                ...editorOptions,\n                readOnly: true,\n              }}\n              onMount={editor => {\n                editorRef.current = editor;\n              }}\n            />\n          </Paper>\n        </Flex>\n      </Container>\n    </Layout>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/ConverterLayout/options.ts",
    "content": "import type { EditorProps } from \"@monaco-editor/react\";\n\nexport const editorOptions: EditorProps[\"options\"] = {\n  formatOnPaste: true,\n  formatOnType: true,\n  tabSize: 2,\n  stopRenderingLineAfter: -1,\n  minimap: { enabled: false },\n  stickyScroll: { enabled: false },\n  scrollBeyondLastLine: false,\n};\n"
  },
  {
    "path": "apps/www/src/layout/JSONCrackBrandLogo.tsx",
    "content": "import React from \"react\";\nimport localFont from \"next/font/local\";\nimport Link from \"next/link\";\nimport { Image } from \"@mantine/core\";\nimport styled from \"styled-components\";\n\nconst monaSans = localFont({\n  src: \"../assets/fonts/Mona-Sans.woff2\",\n  variable: \"--mona-sans\",\n  display: \"swap\",\n  fallback: [\"Futura, Helvetica, sans-serif\", \"Tahoma, Verdana, sans-serif\"],\n});\n\nconst StyledLogoWrapper = styled.div`\n  display: flex;\n  align-items: center;\n  gap: 8px;\n`;\n\nconst StyledTitle = styled.span<{ fontSize: string }>`\n  font-weight: 800;\n  margin: 0;\n  font-family: ${monaSans.style.fontFamily} !important;\n  font-size: ${({ fontSize }) => fontSize};\n  white-space: nowrap;\n  z-index: 10;\n  vertical-align: middle;\n  color: white;\n  mix-blend-mode: difference;\n`;\n\ninterface LogoProps {\n  fontSize?: string;\n  hideLogo?: boolean;\n  hideText?: boolean;\n}\n\nexport const JSONCrackLogo = ({ fontSize = \"1.2rem\", hideText, hideLogo }: LogoProps) => {\n  const handleLogoClick = React.useCallback((event: React.MouseEvent<HTMLAnchorElement>) => {\n    if (typeof window === \"undefined\") return;\n    if (!window.location.href.includes(\"widget\")) return;\n\n    event.preventDefault();\n    window.open(\"/\", \"_blank\", \"noopener,noreferrer\");\n  }, []);\n\n  return (\n    <Link href=\"/\" prefetch={false} target=\"_self\" onClick={handleLogoClick}>\n      <StyledLogoWrapper>\n        {!hideLogo && (\n          <Image\n            src=\"/assets/192.png\"\n            loading=\"eager\"\n            width={parseFloat(fontSize) * 18}\n            height={parseFloat(fontSize) * 18}\n            alt=\"logo\"\n            radius={4}\n            mb=\"2\"\n          />\n        )}\n        {!hideText && <StyledTitle fontSize={fontSize}>JSON CRACK</StyledTitle>}\n      </StyledLogoWrapper>\n    </Link>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/Landing/FAQ.tsx",
    "content": "import React from \"react\";\nimport { Container, Title, Accordion } from \"@mantine/core\";\nimport Questions from \"../../data/faq.json\";\n\nexport const FAQ = () => {\n  return (\n    <Container id=\"faq\" component=\"section\" size=\"sm\" py={80}>\n      <Title\n        c=\"black\"\n        order={2}\n        fz={{\n          base: 24,\n          xs: 30,\n          sm: 36,\n        }}\n        fw={600}\n        mb={60}\n        ta=\"center\"\n      >\n        Frequently Asked Questions\n      </Title>\n      <Accordion\n        variant=\"separated\"\n        styles={{\n          panel: {\n            background: \"#f9f9f9\",\n            color: \"#1d1d1d\",\n          },\n          label: {\n            color: \"#1d1d1d\",\n            fontWeight: 500,\n          },\n          item: {\n            background: \"#f9f9f9\",\n            color: \"#1d1d1d\",\n            overflow: \"hidden\",\n            border: \"1px solid #ededed\",\n            borderRadius: 12,\n            fontWeight: 300,\n          },\n        }}\n      >\n        {Questions.map(({ title, content }) => (\n          <Accordion.Item key={title} value={title}>\n            <Accordion.Control>{title}</Accordion.Control>\n            <Accordion.Panel>{content}</Accordion.Panel>\n          </Accordion.Item>\n        ))}\n      </Accordion>\n    </Container>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/Landing/Features.tsx",
    "content": "import React from \"react\";\nimport {\n  Container,\n  Flex,\n  Title,\n  Text,\n  Paper,\n  Center,\n  Badge,\n  ThemeIcon,\n  SimpleGrid,\n} from \"@mantine/core\";\nimport { FaBolt, FaToolbox } from \"react-icons/fa\";\nimport { IoImages, IoShieldCheckmark } from \"react-icons/io5\";\nimport { MdOutlineFormatIndentIncrease, MdOutlineGeneratingTokens } from \"react-icons/md\";\nimport { TbTransformFilled } from \"react-icons/tb\";\nimport { VscJson } from \"react-icons/vsc\";\n\ninterface FeatureItem {\n  title: string;\n  description: string;\n  icon: React.ReactNode;\n  color: string;\n}\n\nconst features: FeatureItem[] = [\n  {\n    title: \"JSON Visualizer\",\n    description:\n      \"Transform your data into interactive graphs or trees as you type. Supports JSON, YAML, CSV, and XML.\",\n    icon: <FaBolt size={20} />,\n    color: \"yellow\",\n  },\n  {\n    title: \"Convert Data\",\n    description:\n      \"Convert JSON to CSV, YAML to JSON, XML to JSON, and more. Our JSON converter supports multiple formats for easy data exchange.\",\n    icon: <TbTransformFilled size={20} />,\n    color: \"orange\",\n  },\n  {\n    title: \"JSON Formatter and JSON Validator\",\n    description:\n      \"Format and beautify your JSON data to make it more readable. Validate JSON, YAML, and CSV.\",\n    icon: <MdOutlineFormatIndentIncrease size={20} />,\n    color: \"green\",\n  },\n  {\n    title: \"Generate Code/Types\",\n    description: \"Generate TypeScript interface, Golang structs, Rust serde, JSON Schema and more.\",\n    icon: <MdOutlineGeneratingTokens size={20} />,\n    color: \"grape\",\n  },\n  {\n    title: \"JSON Schema Generator\",\n    description:\n      \"Validate JSON Schema, create mock data, and generate JSON Schema from various data formats like JSON, YAML, XML, and CSV.\",\n    icon: <VscJson size={20} />,\n    color: \"cyan\",\n  },\n  {\n    title: \"Advanced JSON Tools\",\n    description: \"Decode JWT, randomize data, execute jq (JSON Query), json path commands.\",\n    icon: <FaToolbox size={20} />,\n    color: \"teal.5\",\n  },\n  {\n    title: \"Export Image\",\n    description:\n      \"Export image of the graphs as PNG, JPEG, or SVG. Share your data visualization with others.\",\n    icon: <IoImages size={20} />,\n    color: \"blue.4\",\n  },\n  {\n    title: \"Secure\",\n    description: \"Your data is never stored on our servers. Everything happens on your device.\",\n    icon: <IoShieldCheckmark size={20} />,\n    color: \"gray\",\n  },\n];\n\nexport const Features = () => {\n  return (\n    <Container component=\"section\" id=\"features\" fluid py={80}>\n      <Container size=\"xl\">\n        <Center>\n          <Badge\n            fw=\"600\"\n            tt=\"none\"\n            variant=\"outline\"\n            c=\"blue.7\"\n            color=\"blue.3\"\n            bg=\"blue.0\"\n            size=\"lg\"\n          >\n            Features\n          </Badge>\n        </Center>\n        <Title\n          c=\"black\"\n          order={2}\n          px=\"lg\"\n          fz={{\n            base: 26,\n            xs: 32,\n            sm: 42,\n          }}\n          fw={600}\n          mb={15}\n          style={{ textAlign: \"center\" }}\n        >\n          Explore Your Data Visually\n        </Title>\n        <Title\n          order={3}\n          fw={500}\n          c=\"gray.7\"\n          px=\"lg\"\n          mx=\"auto\"\n          ta=\"center\"\n          mb={50}\n          fz={{ base: 16, sm: 18 }}\n          w={{ base: \"100%\", xs: \"80%\", sm: \"60%\", md: \"40%\" }}\n        >\n          All in one tool for JSON, YAML, CSV, and XML.\n        </Title>\n\n        <SimpleGrid\n          cols={{\n            base: 1,\n            xs: 2,\n            md: 4,\n          }}\n          spacing=\"xl\"\n        >\n          {features.map((feature, index) => (\n            <Paper key={index} bg=\"gray.0\" p=\"lg\" radius=\"md\">\n              <Flex gap=\"sm\" align=\"center\" justify=\"center\" direction=\"column\">\n                <ThemeIcon radius=\"xl\" size=\"xl\" variant=\"light\" color={feature.color}>\n                  {feature.icon}\n                </ThemeIcon>\n                <Title fw={500} ta=\"center\" c=\"gray.9\" order={3}>\n                  {feature.title}\n                </Title>\n                <Text fz=\"sm\" c=\"gray.8\">\n                  {feature.description}\n                </Text>\n              </Flex>\n            </Paper>\n          ))}\n        </SimpleGrid>\n      </Container>\n    </Container>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/Landing/HeroPreview.tsx",
    "content": "import React from \"react\";\nimport { Container, Image } from \"@mantine/core\";\n\nexport const HeroPreview = () => {\n  return (\n    <Container component=\"section\" id=\"preview\" fluid py=\"20\" mx=\"lg\">\n      <Image\n        src=\"./assets/editor.webp\"\n        loading=\"eager\"\n        maw={1036}\n        mx=\"auto\"\n        alt=\"JSON Crack editor preview\"\n        style={{\n          borderRadius: 10,\n          overflow: \"hidden\",\n          border: \"1px solid #c1c1c1\",\n          outline: \"1px solid #c1c1c1\",\n          outlineOffset: \"6px\",\n        }}\n      />\n    </Container>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/Landing/HeroSection.tsx",
    "content": "import React from \"react\";\nimport { Oxygen } from \"next/font/google\";\nimport Link from \"next/link\";\nimport { Stack, Flex, Button } from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport { FaChevronRight, FaGithub, FaStar } from \"react-icons/fa6\";\n\nconst oxygen = Oxygen({\n  subsets: [\"latin-ext\"],\n  weight: [\"700\"],\n});\n\nconst StyledHeroSection = styled.main`\n  position: relative;\n\n  &:before {\n    position: absolute;\n    content: \"\";\n    width: 100%;\n    height: 100%;\n    background-size: 40px 40px;\n    background-image:\n      linear-gradient(to right, #f7f7f7 1px, transparent 1px),\n      linear-gradient(to bottom, #f7f7f7 1px, transparent 1px);\n    image-rendering: pixelated;\n    -webkit-mask-image: linear-gradient(to bottom, transparent, 0%, white, 98%, transparent);\n    mask-image: linear-gradient(to bottom, transparent, 0%, white, 98%, transparent);\n  }\n\n  @media only screen and (max-width: 1240px) {\n    flex-direction: column;\n  }\n`;\n\nconst StyledHeroSectionBody = styled.div`\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  justify-content: center;\n  padding: 6rem 10% 4rem;\n  overflow: hidden;\n  text-align: center;\n  gap: 60px;\n  min-height: 40vh;\n\n  @media only screen and (max-width: 768px) {\n    padding: 6em 16px;\n    padding-top: 10vh;\n  }\n`;\n\nconst StyledHeroTitle = styled.h1`\n  position: relative;\n  font-size: 2.3rem;\n  font-weight: 700;\n  display: inline;\n  color: #120f43;\n  width: fit-content;\n  line-height: 1.15;\n  max-width: 30rem;\n  font-family: ${oxygen.style.fontFamily};\n\n  @media only screen and (min-width: 576px) {\n    font-size: 3.4rem;\n    max-width: 34rem;\n  }\n\n  @media only screen and (min-width: 992px) {\n    font-size: 3.8rem;\n    max-width: 40rem;\n  }\n\n  @media only screen and (min-width: 1400px) {\n    font-size: 4.2rem;\n    max-width: 50rem;\n  }\n`;\n\nconst StyledHeroText = styled.h2`\n  font-size: 14px;\n  color: #4a5568;\n  font-weight: 400;\n  max-width: 75%;\n  margin-top: 1rem;\n  text-align: center;\n\n  strong {\n    font-weight: 400;\n    color: #115fe6;\n  }\n\n  @media only screen and (min-width: 576px) {\n    font-size: 18px;\n    max-width: 80%;\n  }\n\n  @media only screen and (min-width: 1400px) {\n    font-size: 18px;\n    max-width: 60%;\n  }\n`;\n\nexport const HeroSection = ({ stars = 0 }) => {\n  return (\n    <StyledHeroSection>\n      <StyledHeroSectionBody>\n        <Stack flex=\"1\" miw={250} mx=\"auto\" align=\"center\">\n          <Link href=\"https://github.com/AykutSarac/jsoncrack.com\" target=\"_blank\" rel=\"noopener\">\n            <Button\n              variant=\"default\"\n              radius=\"xl\"\n              ta=\"left\"\n              leftSection={<FaGithub size=\"18\" />}\n              rightSection={\n                <Flex ml=\"sm\" c=\"dimmed\" align=\"center\" gap=\"4\">\n                  <FaStar />\n                  {stars.toLocaleString(\"en-US\")}\n                </Flex>\n              }\n            >\n              GitHub\n            </Button>\n          </Link>\n\n          <StyledHeroTitle>Visualize JSON into interactive graphs</StyledHeroTitle>\n          <StyledHeroText>\n            The best online JSON viewer to <strong>visualize</strong>, <strong>format</strong> and{\" \"}\n            <strong>explore</strong>.\n          </StyledHeroText>\n\n          <Flex gap=\"xs\" wrap=\"wrap\" justify=\"center\" hiddenFrom=\"xs\">\n            <Button\n              component=\"a\"\n              color=\"#202842\"\n              href=\"/editor\"\n              size=\"md\"\n              radius=\"md\"\n              rightSection={<FaChevronRight />}\n              fw=\"500\"\n              mt=\"sm\"\n            >\n              Go to Editor\n            </Button>\n          </Flex>\n          <Flex gap=\"lg\" wrap=\"wrap\" justify=\"center\" visibleFrom=\"xs\">\n            <Button\n              component=\"a\"\n              color=\"#202842\"\n              href=\"/editor\"\n              size=\"xl\"\n              radius=\"md\"\n              rightSection={<FaChevronRight />}\n              mt=\"sm\"\n            >\n              Go to Editor\n            </Button>\n          </Flex>\n        </Stack>\n      </StyledHeroSectionBody>\n    </StyledHeroSection>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/Landing/Section1.tsx",
    "content": "import React from \"react\";\nimport { Container, Image, SimpleGrid, Stack, Text, Title } from \"@mantine/core\";\nimport styled from \"styled-components\";\n\nconst StyledImageWrapper = styled.div`\n  position: relative;\n\n  &::after {\n    content: \"\";\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    top: 0;\n    left: 0;\n    padding: 12px;\n    border-radius: 15px;\n    border: 1px solid #e0e0e0;\n    background: #f3f3f3;\n    --line-color-1: #e3e3e3;\n    --line-color-2: #e5e5e5;\n    background-image:\n      linear-gradient(var(--line-color-1) 1.5px, transparent 1.5px),\n      linear-gradient(90deg, var(--line-color-1) 1.5px, transparent 1.5px),\n      linear-gradient(var(--line-color-2) 1px, transparent 1px),\n      linear-gradient(90deg, var(--line-color-2) 1px, transparent 1px);\n    background-position:\n      -1.5px -1.5px,\n      -1.5px -1.5px,\n      -1px -1px,\n      -1px -1px;\n    background-size:\n      100px 100px,\n      100px 100px,\n      20px 20px,\n      20px 20px;\n  }\n\n  img {\n    z-index: 1;\n  }\n`;\n\nexport const Section1 = () => {\n  return (\n    <Container size=\"xl\" py=\"80\">\n      <Title\n        lh=\"1.1\"\n        fz={{\n          base: 26,\n          xs: 46,\n          sm: 52,\n        }}\n        maw=\"16ch\"\n        ta=\"center\"\n        order={2}\n        c=\"gray.9\"\n        mx=\"auto\"\n        mb=\"15\"\n      >\n        Make working with JSON easy\n      </Title>\n      <Title\n        order={3}\n        fw={400}\n        c=\"gray.7\"\n        px=\"lg\"\n        mx=\"auto\"\n        ta=\"center\"\n        mb={50}\n        fz={{ base: 16, sm: 18 }}\n        w={{ base: \"100%\", md: \"600\" }}\n      >\n        JSON Crack eliminates the chaos of raw, messy data, making the complex appear simple and\n        easy to understand.\n      </Title>\n      <SimpleGrid\n        cols={{\n          base: 1,\n          sm: 3,\n        }}\n      >\n        <Stack\n          p=\"lg\"\n          m=\"lg\"\n          maw=\"360\"\n          mx=\"auto\"\n          style={{\n            borderRadius: \"17px\",\n            border: \"1px solid #e0e0e0\",\n          }}\n        >\n          <StyledImageWrapper>\n            <Image src=\"/assets/step1-visual.png\" pos=\"relative\" w=\"100%\" alt=\"upload\" />\n          </StyledImageWrapper>\n          <Title ta=\"center\" c=\"black\" order={3}>\n            Upload your data\n          </Title>\n          <Text ta=\"center\" c=\"gray.7\">\n            Upload your JSON file, URL, or type your data directly into our easy-to-use text editor.\n          </Text>\n        </Stack>\n        <Stack\n          p=\"lg\"\n          m=\"lg\"\n          maw=\"360\"\n          mx=\"auto\"\n          style={{\n            borderRadius: \"17px\",\n            border: \"1px solid #e0e0e0\",\n          }}\n        >\n          <StyledImageWrapper>\n            <Image src=\"/assets/step2-visual.png\" pos=\"relative\" w=\"100%\" alt=\"visualize\" />\n          </StyledImageWrapper>\n          <Title ta=\"center\" c=\"black\" order={3}>\n            Visualize your JSON\n          </Title>\n          <Text ta=\"center\" c=\"gray.7\">\n            Your data will automatically be turned into a visual tree graph so you can quickly\n            understand your data at a glance.\n          </Text>\n        </Stack>\n        <Stack\n          p=\"lg\"\n          m=\"lg\"\n          maw=\"360\"\n          mx=\"auto\"\n          style={{\n            borderRadius: \"17px\",\n            border: \"1px solid #e0e0e0\",\n          }}\n        >\n          <StyledImageWrapper>\n            <Image src=\"/assets/step3-visual.png\" pos=\"relative\" w=\"100%\" alt=\"export image\" />\n          </StyledImageWrapper>\n          <Title ta=\"center\" c=\"black\" order={3}>\n            Export to image\n          </Title>\n          <Text ta=\"center\" c=\"gray.7\">\n            Once you&apos;re satisfied, you can export an image of your graph as PNG, JPEG, or SVG\n            and share with others.\n          </Text>\n        </Stack>\n      </SimpleGrid>\n    </Container>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/Landing/Section2.tsx",
    "content": "import React from \"react\";\nimport {\n  Button,\n  Container,\n  Flex,\n  Image,\n  JsonInput,\n  List,\n  SimpleGrid,\n  Stack,\n  Text,\n  Title,\n} from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport { LuBadgeCheck } from \"react-icons/lu\";\n\nconst StyledDottedContainer = styled.div`\n  position: relative;\n  background-color: #f3f3f3;\n  background-image: radial-gradient(#e0e0e0 3px, transparent 0);\n  background-size: 40px 40px;\n  border: 1px solid #e0e0e0;\n\n  width: 100%;\n  min-width: 300px;\n  max-width: 500px;\n  border-radius: 15px;\n  height: 460px;\n\n  .jc {\n    position: absolute;\n    top: 0;\n    left: 0;\n    padding: 12px;\n    border-radius: 15px;\n    transform: translate(-80px, 10%);\n    border: 1px solid #000;\n    box-shadow: 0px 4px 0px 0px #000;\n    background: #f3f3f3;\n    --line-color-1: #e3e3e3;\n    --line-color-2: #e5e5e5;\n    background-image:\n      linear-gradient(var(--line-color-1) 1.5px, transparent 1.5px),\n      linear-gradient(90deg, var(--line-color-1) 1.5px, transparent 1.5px),\n      linear-gradient(var(--line-color-2) 1px, transparent 1px),\n      linear-gradient(90deg, var(--line-color-2) 1px, transparent 1px);\n    background-position:\n      -1.5px -1.5px,\n      -1.5px -1.5px,\n      -1px -1px,\n      -1px -1px;\n    background-size:\n      100px 100px,\n      100px 100px,\n      20px 20px,\n      20px 20px;\n  }\n\n  .jcode {\n    position: absolute;\n    top: 0;\n    left: 0;\n    transform: translate(80%, 80%);\n    width: 273px;\n    border-radius: 15px;\n    border: 1px solid #000;\n    box-shadow: 0px 4px 0px 0px #000;\n    overflow: hidden;\n  }\n\n  @media only screen and (max-width: 1085px) {\n    display: none;\n  }\n`;\n\nexport const Section2 = () => {\n  return (\n    <Container size=\"xl\" py=\"80\">\n      <Flex justify=\"center\" gap=\"80\" align=\"center\">\n        <Stack maw={634}>\n          <Title\n            lh=\"1.1\"\n            fz={{\n              base: 26,\n              xs: 32,\n              sm: 42,\n            }}\n            maw={500}\n            order={2}\n            c=\"gray.9\"\n          >\n            Don&apos;t waste time with JSON formatters\n          </Title>\n          <Text my=\"md\" c=\"gray.7\" fz={16} maw={510}>\n            Format JSON and transform into a readable graph in seconds. JSON Crack is an open-source\n            online tool that helps you visualize and understand data.\n          </Text>\n          <List\n            fz={{\n              base: 16,\n              xs: 18,\n            }}\n            fw={500}\n            component={SimpleGrid}\n            c=\"gray.8\"\n            icon={<LuBadgeCheck size=\"20\" />}\n          >\n            <SimpleGrid w=\"fit-content\" cols={2}>\n              <List.Item>VS Code Extension</List.Item>\n              <List.Item>Open-source</List.Item>\n              <List.Item>JSON Validator/Formatter</List.Item>\n              <List.Item>Export Image</List.Item>\n            </SimpleGrid>\n          </List>\n          <Button\n            component=\"a\"\n            href=\"/editor\"\n            color=\"#202842\"\n            size=\"lg\"\n            radius=\"md\"\n            w=\"fit-content\"\n            mt=\"sm\"\n          >\n            Open JSON Editor\n          </Button>\n        </Stack>\n        <StyledDottedContainer>\n          <Image className=\"jc\" src=\"/assets/diagram.svg\" alt=\"diagram\" loading=\"lazy\" />\n          <JsonInput\n            w={273}\n            rows={12}\n            className=\"jcode\"\n            styles={{\n              input: {\n                border: \"none\",\n                fontSize: 12,\n              },\n            }}\n            value={JSON.stringify(\n              {\n                squadName: \"Super hero squad\",\n                homeTown: \"Metro City\",\n                formed: 2016,\n                secretBase: \"Super tower\",\n                active: true,\n                members: [\n                  {\n                    name: \"Molecule Man\",\n                    age: 29,\n                    secretIdentity: \"Dan Jukes\",\n                  },\n                  {\n                    name: \"Madame Uppercut\",\n                    age: 39,\n                    secretIdentity: \"Jane Wilson\",\n                  },\n                  {\n                    name: \"Eternal Flame\",\n                    age: 1000000,\n                    secretIdentity: \"Unknown\",\n                  },\n                ],\n              },\n              null,\n              2\n            )}\n          />\n        </StyledDottedContainer>\n      </Flex>\n    </Container>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/Landing/Section3.tsx",
    "content": "import React from \"react\";\nimport {\n  Button,\n  Container,\n  Flex,\n  Image,\n  List,\n  SimpleGrid,\n  Stack,\n  Text,\n  Title,\n} from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport { LuBadgeCheck } from \"react-icons/lu\";\n\nconst StyledDottedContainer = styled.div`\n  position: relative;\n  width: 100%;\n  min-width: 300px;\n  max-width: 500px;\n  border-radius: 15px;\n  height: 460px;\n\n  .jc {\n    position: absolute;\n    top: 0;\n    left: 0;\n    padding: 12px;\n    border-radius: 15px;\n    border: 1px solid #e0e0e0;\n    background: #f3f3f3;\n    --line-color-1: #e3e3e3;\n    --line-color-2: #e5e5e5;\n    background-image:\n      linear-gradient(var(--line-color-1) 1.5px, transparent 1.5px),\n      linear-gradient(90deg, var(--line-color-1) 1.5px, transparent 1.5px),\n      linear-gradient(var(--line-color-2) 1px, transparent 1px),\n      linear-gradient(90deg, var(--line-color-2) 1px, transparent 1px);\n    background-position:\n      -1.5px -1.5px,\n      -1.5px -1.5px,\n      -1px -1px,\n      -1px -1px;\n    background-size:\n      100px 100px,\n      100px 100px,\n      20px 20px,\n      20px 20px;\n  }\n\n  @media only screen and (max-width: 1085px) {\n    display: none;\n  }\n`;\n\nexport const Section3 = () => {\n  return (\n    <Container size=\"xl\" py=\"80\">\n      <Flex justify=\"center\" gap=\"80\" align=\"center\">\n        <StyledDottedContainer>\n          <Image\n            className=\"jc\"\n            src=\"/assets/bf2-image.png\"\n            alt=\"json, csv, yaml, xml\"\n            loading=\"lazy\"\n          />\n        </StyledDottedContainer>\n        <Stack maw={634}>\n          <Title\n            lh=\"1.1\"\n            fz={{\n              base: 26,\n              xs: 32,\n              sm: 42,\n            }}\n            maw={500}\n            order={2}\n            c=\"gray.9\"\n          >\n            Visualize and convert to multiple formats\n          </Title>\n          <Text my=\"md\" c=\"gray.7\" fz={16} maw={510}>\n            JSON Crack supports formats like CSV, YAML, and XML, making it easier to visualize your\n            data, no matter the type.\n          </Text>\n          <List\n            fz={{\n              base: 16,\n              xs: 18,\n            }}\n            fw={500}\n            component={SimpleGrid}\n            c=\"gray.8\"\n            icon={<LuBadgeCheck size=\"20\" />}\n          >\n            <SimpleGrid w=\"fit-content\" cols={2}>\n              <List.Item>JSON to CSV</List.Item>\n              <List.Item>YAML to JSON</List.Item>\n              <List.Item>XML to JSON</List.Item>\n              <List.Item>and more...</List.Item>\n            </SimpleGrid>\n          </List>\n          <Button\n            component=\"a\"\n            href=\"/converter/json-to-yaml\"\n            color=\"#202842\"\n            size=\"lg\"\n            radius=\"md\"\n            w=\"fit-content\"\n            mt=\"sm\"\n          >\n            Open Converter\n          </Button>\n        </Stack>\n      </Flex>\n    </Container>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/PageLayout/Footer.tsx",
    "content": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Anchor, Container, Divider, Flex, Stack, Text, ThemeIcon } from \"@mantine/core\";\nimport { FaDiscord, FaGithub, FaLinkedin } from \"react-icons/fa\";\nimport { FaXTwitter } from \"react-icons/fa6\";\nimport { JSONCrackLogo } from \"../JSONCrackBrandLogo\";\n\nexport const Footer = () => {\n  return (\n    <Container w=\"100%\" mt={60} px={60} pb=\"xl\" bg=\"black\" fluid>\n      <Divider color=\"gray.3\" mb=\"xl\" mx={-60} />\n      <Flex justify=\"space-between\">\n        <Stack gap={4} visibleFrom=\"sm\">\n          <JSONCrackLogo />\n          <Anchor href=\"mailto:contact@todiagram.com\" fz=\"xs\" c=\"dimmed\">\n            contact@todiagram.com\n          </Anchor>\n        </Stack>\n        <Flex gap={60} visibleFrom=\"sm\">\n          <Stack gap=\"xs\">\n            <Text fz=\"sm\" c=\"white\">\n              Product\n            </Text>\n            <Anchor\n              fz=\"sm\"\n              c=\"gray.5\"\n              href=\"https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode\"\n              rel=\"noopener\"\n            >\n              VS Code\n            </Anchor>\n            <Anchor\n              href=\"https://github.com/AykutSarac/jsoncrack.com\"\n              fz=\"sm\"\n              c=\"gray.5\"\n              target=\"_blank\"\n              rel=\"noopener\"\n            >\n              Open Source\n            </Anchor>\n            <Anchor\n              href=\"https://todiagram.com?utm_source=jsoncrack&utm_medium=footer\"\n              fz=\"sm\"\n              c=\"gray.5\"\n              rel=\"noopener\"\n            >\n              ToDiagram\n            </Anchor>\n          </Stack>\n          <Stack gap=\"xs\">\n            <Text fz=\"sm\" c=\"white\">\n              Resources\n            </Text>\n            <Anchor component={Link} prefetch={false} fz=\"sm\" c=\"gray.5\" href=\"/#faq\">\n              FAQ\n            </Anchor>\n            <Anchor component={Link} prefetch={false} fz=\"sm\" c=\"gray.5\" href=\"/docs\">\n              Docs\n            </Anchor>\n          </Stack>\n          <Stack gap=\"xs\">\n            <Text fz=\"sm\" c=\"white\">\n              Social\n            </Text>\n            <Flex gap=\"xs\">\n              <Anchor\n                aria-label=\"LinkedIn\"\n                href=\"https://www.linkedin.com/company/jsoncrack\"\n                fz=\"sm\"\n                rel=\"noopener\"\n              >\n                <ThemeIcon variant=\"transparent\" color=\"gray.5\">\n                  <FaLinkedin size={20} />\n                </ThemeIcon>\n              </Anchor>\n              <Anchor aria-label=\"X\" fz=\"sm\" href=\"https://x.com/jsoncrack\" rel=\"noopener\">\n                <ThemeIcon variant=\"transparent\" color=\"gray.5\">\n                  <FaXTwitter size={20} />\n                </ThemeIcon>\n              </Anchor>\n              <Anchor\n                aria-label=\"GitHub\"\n                href=\"https://github.com/AykutSarac/jsoncrack.com\"\n                fz=\"sm\"\n                rel=\"noopener\"\n              >\n                <ThemeIcon variant=\"transparent\" color=\"gray.5\">\n                  <FaGithub size={20} />\n                </ThemeIcon>\n              </Anchor>\n              <Anchor\n                aria-label=\"Discord\"\n                fz=\"sm\"\n                href=\"https://discord.com/invite/yVyTtCRueq\"\n                rel=\"noopener\"\n              >\n                <ThemeIcon variant=\"transparent\" color=\"gray.5\">\n                  <FaDiscord size={20} />\n                </ThemeIcon>\n              </Anchor>\n            </Flex>\n          </Stack>\n        </Flex>\n      </Flex>\n      <Flex gap=\"xl\">\n        <Text fz=\"sm\" c=\"dimmed\">\n          © {new Date().getFullYear()} JSON Crack\n        </Text>\n        <Anchor component={Link} prefetch={false} fz=\"sm\" c=\"dimmed\" href=\"/legal/terms\">\n          <Text fz=\"sm\" c=\"dimmed\">\n            Terms\n          </Text>\n        </Anchor>\n        <Anchor component={Link} prefetch={false} fz=\"sm\" c=\"dimmed\" href=\"/legal/privacy\">\n          <Text fz=\"sm\" c=\"dimmed\">\n            Privacy\n          </Text>\n        </Anchor>\n      </Flex>\n    </Container>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/PageLayout/Navbar.tsx",
    "content": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Button, Menu, type MenuItemProps, Text, Stack } from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport { LuChevronDown } from \"react-icons/lu\";\nimport { JSONCrackLogo } from \"../JSONCrackBrandLogo\";\n\nconst StyledNavbarWrapper = styled.div`\n  z-index: 3;\n  transition: background 0.2s ease-in-out;\n`;\n\nconst StyledMenuItem = styled(Menu.Item)<MenuItemProps & any>`\n  color: black;\n\n  &[data-hovered] {\n    background-color: #f7f7f7;\n  }\n`;\n\nconst StyledNavbar = styled.nav`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  width: 100%;\n  max-width: 1200px;\n  margin: 0 auto;\n  padding: 16px 24px;\n  background: white;\n  backdrop-filter: blur(5px);\n  -webkit-backdrop-filter: blur(5px);\n\n  @media only screen and (max-width: 768px) {\n    padding: 16px 24px;\n  }\n`;\n\nconst Left = styled.div`\n  display: flex;\n  align-items: center;\n`;\n\nconst Right = styled.div`\n  display: flex;\n  gap: 16px;\n  align-items: center;\n  white-space: nowrap;\n`;\n\nconst Center = styled.div`\n  display: flex;\n  gap: 6px;\n  align-items: center;\n  white-space: nowrap;\n  justify-content: center;\n\n  @media only screen and (max-width: 768px) {\n    display: none;\n  }\n`;\n\nexport const Navbar = () => {\n  return (\n    <StyledNavbarWrapper className=\"navbar\">\n      <StyledNavbar>\n        <Left>\n          <JSONCrackLogo fontSize=\"1.2rem\" />\n        </Left>\n        <Center>\n          <Button\n            component=\"a\"\n            href=\"https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode\"\n            target=\"_blank\"\n            variant=\"subtle\"\n            color=\"black\"\n            size=\"md\"\n            radius=\"md\"\n            rel=\"noopener\"\n          >\n            VS Code\n          </Button>\n          <Button\n            component={Link}\n            prefetch={false}\n            href=\"/docs\"\n            variant=\"subtle\"\n            color=\"black\"\n            size=\"md\"\n            radius=\"md\"\n          >\n            Embed\n          </Button>\n          <Button\n            component=\"a\"\n            href=\"https://github.com/AykutSarac/jsoncrack.com\"\n            target=\"_blank\"\n            variant=\"subtle\"\n            color=\"black\"\n            size=\"md\"\n            radius=\"md\"\n            rel=\"noopener\"\n          >\n            Open Source\n          </Button>\n          <Menu withArrow shadow=\"sm\">\n            <Menu.Target>\n              <Button\n                variant=\"subtle\"\n                color=\"black\"\n                visibleFrom=\"sm\"\n                size=\"md\"\n                radius=\"md\"\n                rightSection={<LuChevronDown />}\n              >\n                Tools\n              </Button>\n            </Menu.Target>\n            <Menu.Dropdown maw={300} bg=\"white\">\n              <StyledMenuItem component={Link} prefetch={false} href=\"/converter/json-to-yaml\">\n                <Stack gap=\"2\">\n                  <Text c=\"black\" size=\"sm\" fw={600}>\n                    Converter\n                  </Text>\n                  <Text size=\"xs\" c=\"gray.6\" lineClamp={2}>\n                    Convert JSON to YAML, CSV to JSON, YAML to XML, and more.\n                  </Text>\n                </Stack>\n              </StyledMenuItem>\n              <StyledMenuItem component={Link} prefetch={false} href=\"/type/json-to-rust\">\n                <Stack gap=\"2\">\n                  <Text c=\"black\" size=\"sm\" fw={600}>\n                    Generate Types\n                  </Text>\n                  <Text size=\"xs\" c=\"gray.6\" lineClamp={2}>\n                    Generate TypeScript types, Golang structs, Rust, and more.\n                  </Text>\n                </Stack>\n              </StyledMenuItem>\n              <StyledMenuItem component={Link} prefetch={false} href=\"/tools/json-schema\">\n                <Stack gap=\"2\">\n                  <Text c=\"black\" size=\"sm\" fw={600}>\n                    JSON Schema\n                  </Text>\n                  <Text size=\"xs\" c=\"gray.6\" lineClamp={2}>\n                    Generate JSON schema from JSON data.\n                  </Text>\n                  <Text size=\"xs\" c=\"gray.6\" lineClamp={2}>\n                    Generate JSON data from JSON schema.\n                  </Text>\n                </Stack>\n              </StyledMenuItem>\n            </Menu.Dropdown>\n          </Menu>\n        </Center>\n        <Right>\n          <Button\n            component=\"a\"\n            href=\"https://todiagram.com?utm_source=jsoncrack&utm_medium=navbar\"\n            variant=\"subtle\"\n            color=\"black\"\n            size=\"md\"\n            radius=\"md\"\n            rel=\"noopener\"\n          >\n            Upgrade\n          </Button>\n          <Button\n            radius=\"md\"\n            component=\"a\"\n            color=\"#202842\"\n            href=\"/editor\"\n            visibleFrom=\"sm\"\n            size=\"md\"\n          >\n            Editor\n          </Button>\n        </Right>\n      </StyledNavbar>\n    </StyledNavbarWrapper>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/PageLayout/index.tsx",
    "content": "import React from \"react\";\nimport { Inter } from \"next/font/google\";\nimport styled, { ThemeProvider } from \"styled-components\";\nimport { lightTheme } from \"../../constants/theme\";\nimport { Footer } from \"./Footer\";\nimport { Navbar } from \"./Navbar\";\n\nconst inter = Inter({\n  subsets: [\"latin-ext\"],\n});\n\nconst StyledLayoutWrapper = styled.div`\n  background: #fff;\n  font-family: ${inter.style.fontFamily};\n  display: flex;\n  flex-direction: column;\n  min-height: 100vh;\n`;\n\nconst ContentWrapper = styled.div`\n  flex: 1;\n`;\n\nconst PageLayout = ({ children }: React.PropsWithChildren) => {\n  return (\n    <ThemeProvider theme={lightTheme}>\n      <StyledLayoutWrapper>\n        <Navbar />\n        <ContentWrapper>{children}</ContentWrapper>\n        <Footer />\n      </StyledLayoutWrapper>\n    </ThemeProvider>\n  );\n};\n\nexport default PageLayout;\n"
  },
  {
    "path": "apps/www/src/layout/TypeLayout/PageLinks.tsx",
    "content": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Anchor, Button, Flex, List, SimpleGrid, Stack } from \"@mantine/core\";\nimport { FaArrowRightLong } from \"react-icons/fa6\";\nimport { formats, TypeLanguage, typeOptions } from \"../../enums/file.enum\";\n\ntype MappedCombinations = {\n  [language: string]: string[]; // Maps each language to an array of programming languages\n};\n\nfunction mapLanguagesToProgramming(\n  languages: string[],\n  programmingLanguages: string[]\n): MappedCombinations {\n  const mappedCombinations: MappedCombinations = {};\n\n  // Iterate over each language\n  languages.forEach(language => {\n    // Assign the array of programming languages to each language key\n    mappedCombinations[language] = programmingLanguages;\n  });\n\n  return mappedCombinations;\n}\n\nconst filterProgrammingLanguages = [TypeLanguage.TypeScript_Combined, TypeLanguage.JSON_SCHEMA];\n\nconst languages = formats.map(format => format.label);\n\nconst programmingLanguages = typeOptions\n  .filter(option => !filterProgrammingLanguages.includes(option.value))\n  .map(option => option.label);\n\nconst groupedLanguages = mapLanguagesToProgramming(languages, programmingLanguages);\n\nexport const PageLinks = () => {\n  return (\n    <Flex justify=\"space-between\" align=\"center\">\n      <Stack gap=\"sm\" py=\"md\" justify=\"center\">\n        <Button\n          component={Link}\n          prefetch={false}\n          href=\"/editor\"\n          radius=\"md\"\n          size=\"sm\"\n          color=\"dark.5\"\n          autoContrast\n          w=\"fit-content\"\n          rightSection={<FaArrowRightLong />}\n          style={{\n            boxShadow: \"rgba(0, 0, 0, 0.12) 0px -3px 0px 0px inset\",\n            border: \"none\",\n          }}\n        >\n          Open JSON Crack\n        </Button>\n      </Stack>\n      <SimpleGrid cols={4} w=\"fit-content\">\n        {Object.entries(groupedLanguages).map(([from, tos]) => (\n          <List key={from} listStyleType=\"none\">\n            {tos.map(to => (\n              <List.Item key={to} c=\"black\">\n                <Anchor\n                  component={Link}\n                  prefetch={false}\n                  c=\"black\"\n                  href={`/type/${from.toLowerCase()}-to-${to.toLowerCase()}`}\n                >\n                  {from} to {to}\n                </Anchor>\n              </List.Item>\n            ))}\n          </List>\n        ))}\n      </SimpleGrid>\n    </Flex>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/layout/TypeLayout/TypegenWrapper.tsx",
    "content": "import React, { useEffect, useRef } from \"react\";\nimport Head from \"next/head\";\nimport { Box, Container, Flex, Paper, Title, Text } from \"@mantine/core\";\nimport { Editor } from \"@monaco-editor/react\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { LuCheck, LuCircleX } from \"react-icons/lu\";\nimport { SEO } from \"../../constants/seo\";\nimport { type FileFormat, formats, type TypeLanguage, typeOptions } from \"../../enums/file.enum\";\nimport { generateType } from \"../../lib/utils/generateType\";\nimport { editorOptions } from \"../ConverterLayout/options\";\nimport Layout from \"../PageLayout\";\nimport { PageLinks } from \"./PageLinks\";\n\ninterface ConverterPagesProps {\n  from: FileFormat;\n  to: TypeLanguage;\n}\n\nexport const TypegenWrapper = ({ from, to }: ConverterPagesProps) => {\n  const editorRef = useRef<any>(null);\n  const [contentHasError, setContentHasError] = React.useState(false);\n  const [originalContent, setOriginalContent] = React.useState(\"\");\n  const [convertedContent, setConvertedContent] = React.useState(\"\");\n\n  const fromLabel = formats.find(({ value }) => value === from)?.label;\n  const toLabel = typeOptions.find(({ value }) => value === to)?.label;\n\n  useEffect(() => {\n    if (!originalContent.length) return;\n\n    (async () => {\n      try {\n        const type = await generateType(originalContent, from, to);\n        setConvertedContent(type);\n        setContentHasError(false);\n      } catch {\n        setContentHasError(true);\n        setConvertedContent(\"\");\n      }\n    })();\n  }, [from, originalContent, to]);\n\n  return (\n    <Layout>\n      <Head>\n        {generateNextSeo({\n          ...SEO,\n          title: `${fromLabel} to ${toLabel} | JSON Crack`,\n          canonical: `https://jsoncrack.com/converter/${from}-to-${to}`,\n          description: `Instantly generate ${toLabel} from ${fromLabel} using this free online tool. Paste your ${fromLabel} and get the generated ${toLabel} instantly.`,\n        })}\n      </Head>\n      <Container mt=\"xl\" size=\"lg\">\n        <Title c=\"black\">\n          {fromLabel} to {toLabel} Converter\n        </Title>\n        <PageLinks />\n        <Flex pt=\"lg\" gap=\"40\">\n          <Paper mah=\"600px\" withBorder flex=\"1\" style={{ overflow: \"hidden\" }}>\n            <Box p=\"xs\" bg=\"gray\">\n              <Flex justify=\"space-between\" align=\"center\">\n                <Text c=\"gray.3\">{fromLabel}</Text>\n                {contentHasError && !!originalContent ? (\n                  <LuCircleX color=\"red\" />\n                ) : (\n                  <LuCheck color=\"lightgreen\" />\n                )}\n              </Flex>\n            </Box>\n            <Editor\n              value={originalContent}\n              onChange={value => setOriginalContent(value || \"\")}\n              language={from}\n              height={500}\n              options={editorOptions}\n            />\n          </Paper>\n          <Paper mah=\"600px\" withBorder flex=\"1\" style={{ overflow: \"hidden\" }}>\n            <Box p=\"xs\" bg=\"gray\">\n              <Text c=\"gray.3\">{toLabel}</Text>\n            </Box>\n            <Editor\n              value={convertedContent}\n              language={to}\n              height={500}\n              options={{\n                ...editorOptions,\n                readOnly: true,\n              }}\n              onMount={editor => {\n                editorRef.current = editor;\n              }}\n            />\n          </Paper>\n        </Flex>\n      </Container>\n    </Layout>\n  );\n};\n"
  },
  {
    "path": "apps/www/src/lib/utils/generateType.ts",
    "content": "import { type FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { contentToJson } from \"./jsonAdapter\";\n\nexport const generateType = async (input: string, format: FileFormat, output: TypeLanguage) => {\n  try {\n    const inputToJson = await contentToJson(input, format);\n    const jsonString = JSON.stringify(inputToJson);\n\n    if (output === TypeLanguage.Go) {\n      const json2go = await import(\"./json2go.js\");\n      const gofmt = await import(\"gofmt.js\");\n      const types = json2go.default(jsonString);\n\n      return gofmt.default(types.go);\n    } else {\n      const { run } = await import(\"json_typegen_wasm\");\n      return run(\"Root\", jsonString, JSON.stringify({ output_mode: output }));\n    }\n  } catch (error) {\n    console.error(error);\n    return \"\";\n  }\n};\n"
  },
  {
    "path": "apps/www/src/lib/utils/helpers.ts",
    "content": "export function isIframe() {\n  try {\n    return window.self !== window.top;\n  } catch {\n    return true;\n  }\n}\n"
  },
  {
    "path": "apps/www/src/lib/utils/json2go.js",
    "content": "/*\n\tJSON-to-Go\n\tby Matt Holt\n\n\thttps://github.com/mholt/json-to-go\n\n\tA simple utility to translate JSON into a Go type definition.\n*/\n\nfunction jsonToGo(json, typename, flatten = true, example = false, allOmitempty = false) {\n  let data;\n  let scope;\n  let go = \"\";\n  let tabs = 0;\n\n  const seen = {};\n  const stack = [];\n  let accumulator = \"\";\n  const innerTabs = 0;\n  let parent = \"\";\n\n  try {\n    data = JSON.parse(json.replace(/(:\\s*\\[?\\s*-?\\d*)\\.0/g, \"$1.1\")); // hack that forces floats to stay as floats\n    scope = data;\n  } catch (e) {\n    return {\n      go: \"\",\n      error: e.message,\n    };\n  }\n\n  typename = format(typename || \"Root\");\n  append(`type ${typename} `);\n\n  parseScope(scope);\n\n  return {\n    go: flatten ? (go += accumulator) : go,\n  };\n\n  function parseScope(scope, depth = 0) {\n    if (typeof scope === \"object\" && scope !== null) {\n      if (Array.isArray(scope)) {\n        let sliceType;\n        const scopeLength = scope.length;\n\n        for (let i = 0; i < scopeLength; i++) {\n          const thisType = goType(scope[i]);\n          if (!sliceType) sliceType = thisType;\n          else if (sliceType != thisType) {\n            sliceType = mostSpecificPossibleGoType(thisType, sliceType);\n            if (sliceType == \"any\") break;\n          }\n        }\n\n        const slice = flatten && [\"struct\", \"slice\"].includes(sliceType) ? `[]${parent}` : \"[]\";\n\n        if (flatten && depth >= 2) appender(slice);\n        else append(slice);\n        if (sliceType == \"struct\") {\n          const allFields = {};\n\n          // for each field counts how many times appears\n          for (let i = 0; i < scopeLength; i++) {\n            const keys = Object.keys(scope[i]);\n            for (const k in keys) {\n              let keyname = keys[k];\n              if (!(keyname in allFields)) {\n                allFields[keyname] = {\n                  value: scope[i][keyname],\n                  count: 0,\n                };\n              } else {\n                const existingValue = allFields[keyname].value;\n                const currentValue = scope[i][keyname];\n\n                if (compareObjects(existingValue, currentValue)) {\n                  const comparisonResult = compareObjectKeys(\n                    Object.keys(currentValue),\n                    Object.keys(existingValue)\n                  );\n                  if (!comparisonResult) {\n                    keyname = `${keyname}_${uuidv4()}`;\n                    allFields[keyname] = {\n                      value: currentValue,\n                      count: 0,\n                    };\n                  }\n                }\n              }\n              allFields[keyname].count++;\n            }\n          }\n\n          // create a common struct with all fields found in the current array\n          // omitempty dict indicates if a field is optional\n          const keys = Object.keys(allFields),\n            struct = {},\n            omitempty = {};\n          for (const k in keys) {\n            const keyname = keys[k],\n              elem = allFields[keyname];\n\n            struct[keyname] = elem.value;\n            omitempty[keyname] = elem.count != scopeLength;\n          }\n          parseStruct(depth + 1, innerTabs, struct, omitempty); // finally parse the struct !!\n        } else if (sliceType == \"slice\") {\n          parseScope(scope[0], depth);\n        } else {\n          if (flatten && depth >= 2) {\n            appender(sliceType || \"any\");\n          } else {\n            append(sliceType || \"any\");\n          }\n        }\n      } else {\n        if (flatten) {\n          if (depth >= 2) {\n            appender(parent);\n          } else {\n            append(parent);\n          }\n        }\n        parseStruct(depth + 1, innerTabs, scope);\n      }\n    } else {\n      if (flatten && depth >= 2) {\n        appender(goType(scope));\n      } else {\n        append(goType(scope));\n      }\n    }\n  }\n\n  function parseStruct(depth, innerTabs, scope, omitempty) {\n    if (flatten) {\n      stack.push(depth >= 2 ? \"\\n\" : \"\");\n    }\n\n    const seenTypeNames = [];\n\n    if (flatten && depth >= 2) {\n      const parentType = `type ${parent}`;\n      const scopeKeys = formatScopeKeys(Object.keys(scope));\n\n      // this can only handle two duplicate items\n      // future improvement will handle the case where there could\n      // three or more duplicate keys with different values\n      if (parent in seen && compareObjectKeys(scopeKeys, seen[parent])) {\n        stack.pop();\n        return;\n      }\n      seen[parent] = scopeKeys;\n\n      appender(`${parentType} struct {\\n`);\n      ++innerTabs;\n      const keys = Object.keys(scope);\n      for (const i in keys) {\n        const keyname = getOriginalName(keys[i]);\n        indenter(innerTabs);\n        const typename = uniqueTypeName(format(keyname), seenTypeNames);\n        seenTypeNames.push(typename);\n\n        appender(typename + \" \");\n        parent = typename;\n        parseScope(scope[keys[i]], depth);\n        appender(' `json:\"' + keyname);\n        if (allOmitempty || (omitempty && omitempty[keys[i]] === true)) {\n          appender(\",omitempty\");\n        }\n        appender('\"`\\n');\n      }\n      indenter(--innerTabs);\n      appender(\"}\");\n    } else {\n      append(\"struct {\\n\");\n      ++tabs;\n      const keys = Object.keys(scope);\n      for (const i in keys) {\n        const keyname = getOriginalName(keys[i]);\n        indent(tabs);\n        const typename = uniqueTypeName(format(keyname), seenTypeNames);\n        seenTypeNames.push(typename);\n        append(typename + \" \");\n        parent = typename;\n        parseScope(scope[keys[i]], depth);\n        append(' `json:\"' + keyname);\n        if (allOmitempty || (omitempty && omitempty[keys[i]] === true)) {\n          append(\",omitempty\");\n        }\n        if (example && scope[keys[i]] !== \"\" && typeof scope[keys[i]] !== \"object\") {\n          append('\" example:\"' + scope[keys[i]]);\n        }\n        append('\"`\\n');\n      }\n      indent(--tabs);\n      append(\"}\");\n    }\n    if (flatten) accumulator += stack.pop();\n  }\n\n  function indent(tabs) {\n    for (let i = 0; i < tabs; i++) go += \"\\t\";\n  }\n\n  function append(str) {\n    go += str;\n  }\n\n  function indenter(tabs) {\n    for (let i = 0; i < tabs; i++) stack[stack.length - 1] += \"\\t\";\n  }\n\n  function appender(str) {\n    stack[stack.length - 1] += str;\n  }\n\n  // Generate a unique name to avoid duplicate struct field names.\n  // This function appends a number at the end of the field name.\n  function uniqueTypeName(name, seen) {\n    if (seen.indexOf(name) === -1) {\n      return name;\n    }\n\n    let i = 0;\n    while (true) {\n      const newName = name + i.toString();\n      if (seen.indexOf(newName) === -1) {\n        return newName;\n      }\n\n      i++;\n    }\n  }\n\n  // Sanitizes and formats a string to make an appropriate identifier in Go\n  function format(str) {\n    str = formatNumber(str);\n\n    const sanitized = toProperCase(str).replace(/[^a-z0-9]/gi, \"\");\n    if (!sanitized) {\n      return \"NAMING_FAILED\";\n    }\n\n    // After sanitizing the remaining characters can start with a number.\n    // Run the sanitized string again trough formatNumber to make sure the identifier is Num[0-9] or Zero_... instead of 1.\n    return formatNumber(sanitized);\n  }\n\n  // Adds a prefix to a number to make an appropriate identifier in Go\n  function formatNumber(str) {\n    if (!str) return \"\";\n    else if (str.match(/^\\d+$/)) str = \"Num\" + str;\n    else if (str.charAt(0).match(/\\d/)) {\n      const numbers = {\n        0: \"Zero_\",\n        1: \"One_\",\n        2: \"Two_\",\n        3: \"Three_\",\n        4: \"Four_\",\n        5: \"Five_\",\n        6: \"Six_\",\n        7: \"Seven_\",\n        8: \"Eight_\",\n        9: \"Nine_\",\n      };\n      str = numbers[str.charAt(0)] + str.substr(1);\n    }\n\n    return str;\n  }\n\n  // Determines the most appropriate Go type\n  function goType(val) {\n    if (val === null) return \"any\";\n\n    switch (typeof val) {\n      case \"string\":\n        if (/\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(\\.\\d+)?(\\+\\d\\d:\\d\\d|Z)/.test(val)) return \"time.Time\";\n        else return \"string\";\n      case \"number\":\n        if (val % 1 === 0) {\n          if (val > -2147483648 && val < 2147483647) return \"int\";\n          else return \"int64\";\n        } else return \"float64\";\n      case \"boolean\":\n        return \"bool\";\n      case \"object\":\n        if (Array.isArray(val)) return \"slice\";\n        return \"struct\";\n      default:\n        return \"any\";\n    }\n  }\n\n  // Given two types, returns the more specific of the two\n  function mostSpecificPossibleGoType(typ1, typ2) {\n    if (typ1.substr(0, 5) == \"float\" && typ2.substr(0, 3) == \"int\") return typ1;\n    else if (typ1.substr(0, 3) == \"int\" && typ2.substr(0, 5) == \"float\") return typ2;\n    else return \"any\";\n  }\n\n  // Proper cases a string according to Go conventions\n  function toProperCase(str) {\n    // ensure that the SCREAMING_SNAKE_CASE is converted to snake_case\n    if (str.match(/^[_A-Z0-9]+$/)) {\n      str = str.toLowerCase();\n    }\n\n    // https://github.com/golang/lint/blob/5614ed5bae6fb75893070bdc0996a68765fdd275/lint.go#L771-L810\n    const commonInitialisms = [\n      \"ACL\",\n      \"API\",\n      \"ASCII\",\n      \"CPU\",\n      \"CSS\",\n      \"DNS\",\n      \"EOF\",\n      \"GUID\",\n      \"HTML\",\n      \"HTTP\",\n      \"HTTPS\",\n      \"ID\",\n      \"IP\",\n      \"JSON\",\n      \"LHS\",\n      \"QPS\",\n      \"RAM\",\n      \"RHS\",\n      \"RPC\",\n      \"SLA\",\n      \"SMTP\",\n      \"SQL\",\n      \"SSH\",\n      \"TCP\",\n      \"TLS\",\n      \"TTL\",\n      \"UDP\",\n      \"UI\",\n      \"UID\",\n      \"UUID\",\n      \"URI\",\n      \"URL\",\n      \"UTF8\",\n      \"VM\",\n      \"XML\",\n      \"XMPP\",\n      \"XSRF\",\n      \"XSS\",\n    ];\n\n    return str\n      .replace(/(^|[^a-zA-Z])([a-z]+)/g, function (unused, sep, frag) {\n        if (commonInitialisms.indexOf(frag.toUpperCase()) >= 0) return sep + frag.toUpperCase();\n        else return sep + frag[0].toUpperCase() + frag.substr(1).toLowerCase();\n      })\n      .replace(/([A-Z])([a-z]+)/g, function (unused, sep, frag) {\n        if (commonInitialisms.indexOf(sep + frag.toUpperCase()) >= 0)\n          return (sep + frag).toUpperCase();\n        else return sep + frag;\n      });\n  }\n\n  function uuidv4() {\n    return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n      var r = (Math.random() * 16) | 0,\n        v = c == \"x\" ? r : (r & 0x3) | 0x8;\n      return v.toString(16);\n    });\n  }\n\n  function getOriginalName(unique) {\n    const reLiteralUUID =\n      /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n    const uuidLength = 36;\n\n    if (unique.length >= uuidLength) {\n      const tail = unique.substr(-uuidLength);\n      if (reLiteralUUID.test(tail)) {\n        return unique.slice(0, -1 * (uuidLength + 1));\n      }\n    }\n    return unique;\n  }\n\n  function compareObjects(objectA, objectB) {\n    const object = \"[object Object]\";\n    return (\n      Object.prototype.toString.call(objectA) === object &&\n      Object.prototype.toString.call(objectB) === object\n    );\n  }\n\n  function compareObjectKeys(itemAKeys, itemBKeys) {\n    const lengthA = itemAKeys.length;\n    const lengthB = itemBKeys.length;\n\n    // nothing to compare, probably identical\n    if (lengthA == 0 && lengthB == 0) return true;\n\n    // duh\n    if (lengthA != lengthB) return false;\n\n    for (const item of itemAKeys) {\n      if (!itemBKeys.includes(item)) return false;\n    }\n    return true;\n  }\n\n  function formatScopeKeys(keys) {\n    for (const i in keys) {\n      keys[i] = format(keys[i]);\n    }\n    return keys;\n  }\n}\n\nif (typeof module != \"undefined\") {\n  module.exports = jsonToGo;\n}\n"
  },
  {
    "path": "apps/www/src/lib/utils/jsonAdapter.ts",
    "content": "import type { ParseError } from \"jsonc-parser\";\nimport { FileFormat } from \"../../enums/file.enum\";\n\nexport const contentToJson = async (value: string, format = FileFormat.JSON): Promise<object> => {\n  if (!value) return {};\n\n  if (format === FileFormat.JSON) {\n    const { parse } = await import(\"jsonc-parser\");\n    const errors: ParseError[] = [];\n    const result = parse(value, errors);\n    if (errors.length > 0) JSON.parse(value);\n    return result;\n  }\n\n  if (format === FileFormat.YAML) {\n    const { load } = await import(\"js-yaml\");\n    return load(value) as object;\n  }\n\n  if (format === FileFormat.XML) {\n    const { XMLParser } = await import(\"fast-xml-parser\");\n    const parser = new XMLParser({\n      attributeNamePrefix: \"$\",\n      ignoreAttributes: false,\n      allowBooleanAttributes: true,\n      parseAttributeValue: true,\n      trimValues: true,\n      parseTagValue: true,\n    });\n    return parser.parse(value);\n  }\n\n  if (format === FileFormat.CSV) {\n    const { csv2json } = await import(\"json-2-csv\");\n    const result = csv2json(value, {\n      trimFieldValues: true,\n      trimHeaderFields: true,\n      wrapBooleans: true,\n      excelBOM: true,\n    });\n    return result;\n  }\n\n  return {};\n};\n\nexport const jsonToContent = async (json: string, format: FileFormat): Promise<string> => {\n  try {\n    if (!json) return \"\";\n\n    if (format === FileFormat.JSON) {\n      const parsedJson = JSON.parse(json);\n      return JSON.stringify(parsedJson, null, 2);\n    }\n\n    if (format === FileFormat.YAML) {\n      const { dump } = await import(\"js-yaml\");\n      const { parse } = await import(\"jsonc-parser\");\n      return dump(parse(json));\n    }\n\n    if (format === FileFormat.XML) {\n      const { XMLBuilder } = await import(\"fast-xml-parser\");\n      const builder = new XMLBuilder({\n        format: true,\n        attributeNamePrefix: \"$\",\n        ignoreAttributes: false,\n      });\n\n      return builder.build(JSON.parse(json));\n    }\n\n    if (format === FileFormat.CSV) {\n      const { json2csv } = await import(\"json-2-csv\");\n      const parsedJson = JSON.parse(json);\n\n      const data = Array.isArray(parsedJson) ? parsedJson : [parsedJson];\n      return json2csv(data, {\n        expandArrayObjects: true,\n        expandNestedObjects: true,\n        excelBOM: true,\n        wrapBooleans: true,\n        trimFieldValues: true,\n        trimHeaderFields: true,\n      });\n    }\n\n    return json;\n  } catch (error) {\n    console.error(json, error);\n    return json;\n  }\n};\n"
  },
  {
    "path": "apps/www/src/lib/utils/mantineColorScheme.ts",
    "content": "import { type MantineColorScheme, type MantineColorSchemeManager } from \"@mantine/core\";\n\nexport interface SmartColorSchemeManagerOptions {\n  /** Local storage key used to retrieve value with `localStorage.getItem(key)` */\n  key: string;\n  /** Function that returns the current pathname */\n  getPathname: () => string;\n  /** Optional list of paths that should use the dynamic color scheme */\n  dynamicPaths?: string[];\n}\n\n/**\n * Creates a smart color scheme manager that handles different behaviors based on the current path\n * - For editor paths: Uses dynamic behavior with localStorage persistence and per-tab independence\n * - For other paths: Forces light theme\n */\nexport function smartColorSchemeManager({\n  key,\n  getPathname,\n  dynamicPaths = [],\n}: SmartColorSchemeManagerOptions): MantineColorSchemeManager {\n  // Keep track of theme in memory for dynamic paths\n  let currentColorScheme: MantineColorScheme | null = null;\n\n  // Helper function to check if current path should use dynamic behavior\n  const shouldUseDynamicBehavior = () => {\n    const pathname = getPathname();\n    return dynamicPaths.some(path => pathname === path || pathname.startsWith(`${path}/`));\n  };\n\n  return {\n    get: defaultValue => {\n      // If not in a dynamic path (e.g., editor), always return light theme\n      if (!shouldUseDynamicBehavior()) return \"light\";\n\n      // For dynamic paths, use the stored value (memory first, then localStorage)\n      if (currentColorScheme) return currentColorScheme;\n\n      // First time initialization - read from localStorage\n      if (typeof window === \"undefined\") return defaultValue;\n\n      try {\n        currentColorScheme =\n          (window.localStorage.getItem(key) as MantineColorScheme) || defaultValue;\n        return currentColorScheme;\n      } catch {\n        return defaultValue;\n      }\n    },\n\n    set: value => {\n      // Only store theme for dynamic paths\n      if (!shouldUseDynamicBehavior()) return;\n\n      // Update our in-memory value\n      currentColorScheme = value;\n\n      // Also save to localStorage\n      try {\n        window.localStorage.setItem(key, value);\n      } catch (error) {\n        console.warn(\"Smart color scheme manager was unable to save color scheme.\", error);\n      }\n    },\n\n    // These do nothing regardless of path\n    subscribe: () => {},\n    unsubscribe: () => {},\n    clear: () => {\n      currentColorScheme = null;\n      if (typeof window !== \"undefined\") {\n        window.localStorage.removeItem(key);\n      }\n    },\n  };\n}\n"
  },
  {
    "path": "apps/www/src/lib/utils/search.ts",
    "content": "export const searchQuery = (param: string) => {\n  return document.querySelectorAll(param);\n};\n\nexport const cleanupHighlight = () => {\n  const nodes = document.querySelectorAll(\"foreignObject.searched, .highlight\");\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    node.classList.remove(\"searched\", \"highlight\");\n  }\n};\n\nexport const highlightMatchedNodes = (nodes: NodeListOf<Element>, selectedNode: number) => {\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i];\n    const foreignObject = node.parentElement?.closest(\"foreignObject\");\n    if (foreignObject) foreignObject.classList.add(\"searched\");\n  }\n\n  nodes[selectedNode].classList.add(\"highlight\");\n};\n"
  },
  {
    "path": "apps/www/src/pages/404.tsx",
    "content": "import React from \"react\";\nimport Head from \"next/head\";\nimport Link from \"next/link\";\nimport { Button, Stack, Text, Title } from \"@mantine/core\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { SEO } from \"../constants/seo\";\nimport Layout from \"../layout/PageLayout\";\n\nconst NotFound = () => {\n  return (\n    <Layout>\n      <Head>{generateNextSeo({ ...SEO, title: \"404 | JSON Crack\", noindex: true })}</Head>\n      <Stack mt={100} justify=\"center\" align=\"center\">\n        <Title fz={150} style={{ fontFamily: \"monospace\" }}>\n          404\n        </Title>\n        <Title order={2}>Nothing to see here</Title>\n        <Text c=\"dimmed\" maw={800} style={{ textAlign: \"center\" }}>\n          Page you are trying to open does not exist. You may have mistyped the address, or the page\n          has been moved to another URL. If you think this is an error contact support.\n        </Text>\n        <Link href=\"/\">\n          <Button size=\"lg\" color=\"gray\" type=\"button\">\n            Go Home\n          </Button>\n        </Link>\n      </Stack>\n    </Layout>\n  );\n};\n\nexport default NotFound;\n"
  },
  {
    "path": "apps/www/src/pages/_app.tsx",
    "content": "import type { AppProps } from \"next/app\";\nimport Head from \"next/head\";\nimport { useRouter } from \"next/router\";\nimport { createTheme, MantineProvider } from \"@mantine/core\";\nimport \"@mantine/core/styles.css\";\nimport { CodeHighlightAdapterProvider, createShikiAdapter } from \"@mantine/code-highlight\";\nimport \"@mantine/code-highlight/styles.css\";\nimport { ThemeProvider } from \"styled-components\";\nimport \"jsoncrack-react/style.css\";\nimport { SoftwareApplicationJsonLd } from \"next-seo\";\nimport { generateDefaultSeo } from \"next-seo/pages\";\nimport { GoogleAnalytics } from \"nextjs-google-analytics\";\nimport { Toaster } from \"react-hot-toast\";\nimport GlobalStyle from \"../constants/globalStyle\";\nimport { SEO } from \"../constants/seo\";\nimport { lightTheme } from \"../constants/theme\";\nimport { smartColorSchemeManager } from \"../lib/utils/mantineColorScheme\";\n\nasync function loadShiki() {\n  const { createHighlighter } = await import(\"shiki\");\n  const shiki = await createHighlighter({\n    langs: [\"typescript\", \"json\", \"go\", \"kotlin\", \"rust\"],\n    themes: [],\n  });\n\n  return shiki;\n}\n\nconst shikiAdapter = createShikiAdapter(loadShiki);\n\nconst theme = createTheme({\n  autoContrast: true,\n  fontSmoothing: false,\n  respectReducedMotion: true,\n  cursorType: \"pointer\",\n  fontFamily:\n    'system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\"',\n  defaultGradient: {\n    from: \"#388cdb\",\n    to: \"#0f037f\",\n    deg: 180,\n  },\n  primaryShade: 8,\n  colors: {\n    brightBlue: [\n      \"#e6f2ff\",\n      \"#cee1ff\",\n      \"#9bc0ff\",\n      \"#649dff\",\n      \"#3980fe\",\n      \"#1d6dfe\",\n      \"#0964ff\",\n      \"#0054e4\",\n      \"#004acc\",\n      \"#003fb5\",\n    ],\n  },\n  radius: {\n    lg: \"12px\",\n  },\n  components: {\n    Button: {\n      defaultProps: {\n        fw: 500,\n      },\n    },\n  },\n});\n\nfunction JSONCrackApp({ Component, pageProps }: AppProps) {\n  const { pathname } = useRouter();\n\n  // Create a single smart manager that handles pathname logic internally\n  const colorSchemeManager = smartColorSchemeManager({\n    key: \"editor-color-scheme\",\n    getPathname: () => pathname,\n    dynamicPaths: [\"/editor\"], // Only editor paths use dynamic theme\n  });\n\n  return (\n    <>\n      <Head>{generateDefaultSeo(SEO)}</Head>\n      <SoftwareApplicationJsonLd\n        name=\"JSON Crack\"\n        type=\"SoftwareApplication\"\n        operatingSystem=\"Browser\"\n        applicationCategory=\"DeveloperApplication\"\n        aggregateRating={{ ratingValue: 4.9, ratingCount: 19 }}\n        datePublished=\"2022-17-02\"\n      />\n      <MantineProvider\n        colorSchemeManager={colorSchemeManager}\n        defaultColorScheme=\"light\"\n        theme={theme}\n      >\n        <CodeHighlightAdapterProvider adapter={shikiAdapter}>\n          <ThemeProvider theme={lightTheme}>\n            <Toaster\n              position=\"bottom-right\"\n              containerStyle={{\n                bottom: 34,\n                right: 8,\n                fontSize: 14,\n              }}\n              toastOptions={{\n                style: {\n                  background: \"#4D4D4D\",\n                  color: \"#B9BBBE\",\n                  borderRadius: 4,\n                },\n              }}\n            />\n            <GlobalStyle />\n            {process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID && <GoogleAnalytics trackPageViews />}\n            <Component {...pageProps} />\n          </ThemeProvider>\n        </CodeHighlightAdapterProvider>\n      </MantineProvider>\n    </>\n  );\n}\n\nexport default JSONCrackApp;\n"
  },
  {
    "path": "apps/www/src/pages/_document.tsx",
    "content": "import type { DocumentContext, DocumentInitialProps } from \"next/document\";\nimport Document, { Html, Head, Main, NextScript } from \"next/document\";\nimport { ColorSchemeScript } from \"@mantine/core\";\nimport { ServerStyleSheet } from \"styled-components\";\n\nclass MyDocument extends Document {\n  static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {\n    const sheet = new ServerStyleSheet();\n    const originalRenderPage = ctx.renderPage;\n\n    try {\n      ctx.renderPage = () =>\n        originalRenderPage({\n          enhanceApp: App => props => sheet.collectStyles(<App {...props} />),\n        });\n\n      const initialProps = await Document.getInitialProps(ctx);\n\n      return {\n        ...initialProps,\n        styles: (\n          <>\n            {initialProps.styles}\n            {sheet.getStyleElement()}\n          </>\n        ),\n      };\n    } finally {\n      sheet.seal();\n    }\n  }\n\n  render() {\n    return (\n      <Html lang=\"en\">\n        <Head>\n          <ColorSchemeScript />\n        </Head>\n        <body>\n          <Main />\n          <NextScript />\n        </body>\n      </Html>\n    );\n  }\n}\n\nexport default MyDocument;\n"
  },
  {
    "path": "apps/www/src/pages/_error.tsx",
    "content": "import React from \"react\";\nimport Head from \"next/head\";\nimport { useRouter } from \"next/router\";\nimport { Button, Stack, Text, Title } from \"@mantine/core\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { SEO } from \"../constants/seo\";\nimport Layout from \"../layout/PageLayout\";\n\nconst Custom500 = () => {\n  const router = useRouter();\n\n  return (\n    <Layout>\n      <Head>\n        {generateNextSeo({\n          ...SEO,\n          title: \"Unexpected Error Occurred | JSON Crack\",\n        })}\n      </Head>\n      <Stack mt={100} justify=\"center\" align=\"center\">\n        <Title fz={150} style={{ fontFamily: \"monospace\" }}>\n          500\n        </Title>\n        <Title order={2}>Something bad just happened...</Title>\n        <Text c=\"dimmed\" maw={800} style={{ textAlign: \"center\" }}>\n          Our servers could not handle your request. Don&apos;t worry, our development team was\n          already notified. Try refreshing the page.\n        </Text>\n        <Button size=\"lg\" color=\"gray\" type=\"button\" onClick={() => router.reload()}>\n          Refresh the page\n        </Button>\n      </Stack>\n    </Layout>\n  );\n};\n\nexport default Custom500;\n"
  },
  {
    "path": "apps/www/src/pages/converter/csv-to-json.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.CSV} to={FileFormat.JSON} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/csv-to-xml.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.CSV} to={FileFormat.XML} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/csv-to-yaml.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.CSV} to={FileFormat.YAML} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/json-to-csv.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.JSON} to={FileFormat.CSV} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/json-to-xml.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.JSON} to={FileFormat.XML} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/json-to-yaml.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.JSON} to={FileFormat.YAML} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/xml-to-csv.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.XML} to={FileFormat.CSV} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/xml-to-json.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.XML} to={FileFormat.JSON} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/xml-to-yaml.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.XML} to={FileFormat.YAML} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/yaml-to-csv.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.YAML} to={FileFormat.CSV} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/yaml-to-json.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.YAML} to={FileFormat.JSON} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/converter/yaml-to-xml.tsx",
    "content": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/ConverterLayout/ToolPage\";\n\nconst Page = () => {\n  return <ToolPage from={FileFormat.YAML} to={FileFormat.XML} />;\n};\n\nexport default Page;\n"
  },
  {
    "path": "apps/www/src/pages/docs.tsx",
    "content": "import React from \"react\";\nimport Head from \"next/head\";\nimport { Group, Paper, Stack, Text, Title } from \"@mantine/core\";\nimport { CodeHighlight } from \"@mantine/code-highlight\";\nimport styled from \"styled-components\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { SEO } from \"../constants/seo\";\nimport Layout from \"../layout/PageLayout\";\n\nconst StyledFrame = styled.iframe`\n  border: none;\n  width: 80%;\n  flex: 500px;\n  margin: 3% auto;\n`;\n\nconst StyledContentBody = styled.div`\n  display: flex;\n  flex-direction: column;\n  flex-wrap: wrap;\n  gap: 15px;\n  line-height: 1.8;\n  overflow-x: auto;\n`;\n\nconst StyledHighlight = styled.span<{ $link?: boolean; $alert?: boolean }>`\n  display: inline-block;\n  text-align: left;\n  color: ${({ theme, $link, $alert }) =>\n    $alert ? theme.DANGER : $link ? theme.BLURPLE : theme.TEXT_POSITIVE};\n  background: ${({ theme }) => theme.BACKGROUND_TERTIARY};\n  border-radius: 4px;\n  font-weight: 500;\n  padding: 2px 4px;\n  font-size: 14px;\n  margin: ${({ $alert }) => ($alert ? \"8px 0\" : \"1px\")};\n`;\n\nconst Docs = () => {\n  return (\n    <Layout>\n      <Head>\n        {generateNextSeo({\n          ...SEO,\n          title: \"Documentation - JSON Crack\",\n          description: \"Integrate JSON Crack widgets into your website.\",\n          canonical: \"https://jsoncrack.com/docs\",\n        })}\n      </Head>\n      <Stack mx=\"auto\" maw=\"90%\">\n        <Group mb=\"lg\" mt={40}>\n          <Title order={1} c=\"dark\">\n            Embed\n          </Title>\n        </Group>\n        <Paper bg=\"white\" c=\"black\" p=\"md\" radius=\"md\" withBorder>\n          <Title mb=\"sm\" order={3} c=\"dark\">\n            # Fetching from URL\n          </Title>\n          <StyledContentBody>\n            <Text>\n              By adding <StyledHighlight>?json=https://catfact.ninja/fact</StyledHighlight> query at\n              the end of iframe src you will be able to fetch from URL at widgets without additional\n              scripts. This applies to editor page as well, the following link will fetch the url at\n              the editor:{\" \"}\n              <StyledHighlight\n                as=\"a\"\n                href=\"https://jsoncrack.com/editor?json=https://catfact.ninja/fact\"\n                $link\n              >\n                https://jsoncrack.com/editor?json=https://catfact.ninja/fact\n              </StyledHighlight>\n            </Text>\n\n            <StyledFrame\n              title=\"Untitled\"\n              src=\"https://codepen.io/AykutSarac/embed/KKBpWVR?default-tab=html%2Cresult\"\n              loading=\"eager\"\n            >\n              See the Pen <a href=\"https://codepen.io/AykutSarac/pen/KKBpWVR\">Untitled</a> by Aykut\n              Saraç (<a href=\"https://codepen.io/AykutSarac\">@AykutSarac</a>) on{\" \"}\n              <a href=\"https://codepen.io\">CodePen</a>.\n            </StyledFrame>\n          </StyledContentBody>\n        </Paper>\n        <Paper bg=\"white\" c=\"black\" p=\"md\" radius=\"md\" withBorder>\n          <Title mb=\"sm\" order={3} c=\"dark\">\n            # Communicating with API\n          </Title>\n          <Title order={4}>◼︎ Post Message to Embed</Title>\n          <StyledContentBody>\n            <Text>\n              Communicating with the embed is possible with{\" \"}\n              <StyledHighlight\n                as=\"a\"\n                href=\"https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage\"\n                $link\n              >\n                MessagePort\n              </StyledHighlight>\n              , you should pass an object consist of &quot;json&quot; and &quot;options&quot; key\n              where json is a string and options is an object that may contain the following:\n              <CodeHighlight\n                w={500}\n                language=\"json\"\n                code={\n                  '{\\n  theme: \"light\" | \"dark\",\\n  direction: \"TOP\" | \"RIGHT\" | \"DOWN\" | \"LEFT\"\\n}'\n                }\n                withCopyButton={false}\n              />\n            </Text>\n\n            <StyledFrame\n              scrolling=\"no\"\n              title=\"Untitled\"\n              src=\"https://codepen.io/AykutSarac/embed/rNrVyWP?default-tab=html%2Cresult\"\n              loading=\"lazy\"\n            >\n              See the Pen <a href=\"https://codepen.io/AykutSarac/pen/rNrVyWP\">Untitled</a> by Aykut\n              Saraç (<a href=\"https://codepen.io/AykutSarac\">@AykutSarac</a>) on{\" \"}\n              <a href=\"https://codepen.io\">CodePen</a>.\n            </StyledFrame>\n          </StyledContentBody>\n        </Paper>\n        <Paper bg=\"white\" c=\"black\" p=\"md\" radius=\"md\" withBorder>\n          <Title order={4}>◼︎ On Page Load</Title>\n          <StyledContentBody>\n            <Text>\n              <Text>\n                ⚠️ <b>Important!</b> - iframe should be defined before the script tag\n              </Text>\n              <Text>\n                ⚠️ <b>Note</b> - Widget is not loaded immediately with the parent page. The widget\n                sends its <b>id</b> attribute so you can listen for it as in the example below to\n                ensure its loaded and ready to listen for messages.\n              </Text>\n            </Text>\n            <StyledFrame\n              title=\"Untitled\"\n              src=\"https://codepen.io/AykutSarac/embed/QWBbpqx?default-tab=html%2Cresult\"\n              loading=\"lazy\"\n            >\n              See the Pen <a href=\"https://codepen.io/AykutSarac/pen/QWBbpqx\">Untitled</a> by Aykut\n              Saraç (<a href=\"https://codepen.io/AykutSarac\">@AykutSarac</a>) on{\" \"}\n              <a href=\"https://codepen.io\">CodePen</a>.\n            </StyledFrame>\n          </StyledContentBody>\n        </Paper>\n      </Stack>\n    </Layout>\n  );\n};\n\nexport default Docs;\n"
  },
  {
    "path": "apps/www/src/pages/editor.tsx",
    "content": "import { useEffect } from \"react\";\nimport dynamic from \"next/dynamic\";\nimport Head from \"next/head\";\nimport { useRouter } from \"next/router\";\nimport { useMantineColorScheme } from \"@mantine/core\";\nimport \"@mantine/dropzone/styles.css\";\nimport styled, { ThemeProvider } from \"styled-components\";\nimport { Allotment } from \"allotment\";\nimport \"allotment/dist/style.css\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { SEO } from \"../constants/seo\";\nimport { darkTheme, lightTheme } from \"../constants/theme\";\nimport { Banner } from \"../features/Banner\";\nimport { BottomBar } from \"../features/editor/BottomBar\";\nimport { FullscreenDropzone } from \"../features/editor/FullscreenDropzone\";\nimport { Toolbar } from \"../features/editor/Toolbar\";\nimport useGraph from \"../features/editor/views/GraphView/stores/useGraph\";\nimport useConfig from \"../store/useConfig\";\nimport useFile from \"../store/useFile\";\n\nconst ModalController = dynamic(() => import(\"../features/modals/ModalController\"));\nconst ExternalMode = dynamic(() => import(\"../features/editor/ExternalMode\"));\n\nexport const StyledPageWrapper = styled.div`\n  display: flex;\n  flex-direction: column;\n  height: 100vh;\n  width: 100%;\n\n  @media only screen and (max-width: 320px) {\n    height: 100vh;\n  }\n`;\n\nexport const StyledEditorWrapper = styled.div`\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n`;\n\nexport const StyledEditor = styled(Allotment)`\n  position: relative !important;\n  display: flex;\n  background: ${({ theme }) => theme.BACKGROUND_SECONDARY};\n\n  @media only screen and (max-width: 320px) {\n    height: 100vh;\n  }\n`;\n\nconst StyledTextEditor = styled.div`\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  height: 100%;\n`;\n\nconst TextEditor = dynamic(() => import(\"../features/editor/TextEditor\"), {\n  ssr: false,\n});\n\nconst LiveEditor = dynamic(() => import(\"../features/editor/LiveEditor\"), {\n  ssr: false,\n});\n\nconst EditorPage = () => {\n  const { query, isReady } = useRouter();\n  const { setColorScheme } = useMantineColorScheme();\n  const checkEditorSession = useFile(state => state.checkEditorSession);\n  const darkmodeEnabled = useConfig(state => state.darkmodeEnabled);\n  const fullscreen = useGraph(state => state.fullscreen);\n\n  useEffect(() => {\n    if (isReady) checkEditorSession(query?.json);\n  }, [checkEditorSession, isReady, query]);\n\n  useEffect(() => {\n    setColorScheme(darkmodeEnabled ? \"dark\" : \"light\");\n  }, [darkmodeEnabled, setColorScheme]);\n\n  return (\n    <>\n      <Head>\n        {generateNextSeo({\n          ...SEO,\n          title: \"Editor | JSON Crack\",\n          description:\n            \"JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.\",\n          canonical: \"https://jsoncrack.com/editor\",\n        })}\n      </Head>\n      <ThemeProvider theme={darkmodeEnabled ? darkTheme : lightTheme}>\n        <ExternalMode />\n        <ModalController />\n        <StyledEditorWrapper>\n          <StyledPageWrapper>\n            {process.env.NEXT_PUBLIC_DISABLE_EXTERNAL_MODE === \"true\" ? null : <Banner />}\n            <Toolbar />\n            <StyledEditorWrapper>\n              <StyledEditor proportionalLayout={false}>\n                <Allotment.Pane\n                  preferredSize={450}\n                  minSize={fullscreen ? 0 : 300}\n                  maxSize={800}\n                  visible={!fullscreen}\n                >\n                  <StyledTextEditor>\n                    <TextEditor />\n                    <BottomBar />\n                  </StyledTextEditor>\n                </Allotment.Pane>\n                <Allotment.Pane minSize={0}>\n                  <LiveEditor />\n                </Allotment.Pane>\n              </StyledEditor>\n              <FullscreenDropzone />\n            </StyledEditorWrapper>\n          </StyledPageWrapper>\n        </StyledEditorWrapper>\n      </ThemeProvider>\n    </>\n  );\n};\n\nexport default EditorPage;\n"
  },
  {
    "path": "apps/www/src/pages/index.tsx",
    "content": "import React from \"react\";\nimport type { InferGetStaticPropsType, GetStaticProps } from \"next\";\nimport Head from \"next/head\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { SEO } from \"../constants/seo\";\nimport { FAQ } from \"../layout/Landing/FAQ\";\nimport { Features } from \"../layout/Landing/Features\";\nimport { HeroPreview } from \"../layout/Landing/HeroPreview\";\nimport { HeroSection } from \"../layout/Landing/HeroSection\";\nimport { Section1 } from \"../layout/Landing/Section1\";\nimport { Section2 } from \"../layout/Landing/Section2\";\nimport { Section3 } from \"../layout/Landing/Section3\";\nimport Layout from \"../layout/PageLayout\";\n\nexport const HomePage = (props: InferGetStaticPropsType<typeof getStaticProps>) => {\n  return (\n    <Layout>\n      <Head>{generateNextSeo({ ...SEO, canonical: \"https://jsoncrack.com\" })}</Head>\n      <HeroSection stars={props.stars} />\n      <HeroPreview />\n      <Section1 />\n      <Section2 />\n      <Section3 />\n      <Features />\n      <FAQ />\n    </Layout>\n  );\n};\n\nexport default HomePage;\n\nexport const getStaticProps = (async () => {\n  try {\n    const res = await fetch(\"https://api.github.com/repos/AykutSarac/jsoncrack.com\");\n    const data = await res.json();\n\n    return {\n      props: {\n        stars: data?.stargazers_count || 0,\n      },\n    };\n  } catch {\n    return {\n      props: {\n        stars: 0,\n      },\n    };\n  }\n}) satisfies GetStaticProps<{ stars: number }>;\n"
  },
  {
    "path": "apps/www/src/pages/legal/privacy.tsx",
    "content": "import React from \"react\";\nimport Head from \"next/head\";\nimport { Box, Container, Paper, Stack, Text, Title } from \"@mantine/core\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { SEO } from \"../../constants/seo\";\nimport privacy from \"../../data/privacy.json\";\nimport Layout from \"../../layout/PageLayout\";\n\nconst Privacy = () => {\n  return (\n    <Layout>\n      <Head>\n        {generateNextSeo({\n          ...SEO,\n          title: \"Privacy Policy - JSON Crack\",\n          description: \"JSON Crack Privacy Policy\",\n          canonical: \"https://jsoncrack.com/legal/privacy\",\n        })}\n      </Head>\n      <Container my={50} size=\"md\" pb=\"lg\">\n        <Paper bg=\"transparent\">\n          <Title ta=\"center\" c=\"gray.8\">\n            Privacy Policy\n          </Title>\n          <Text c=\"gray.6\" ta=\"center\">\n            Last updated: Feb 5, 2025\n          </Text>\n\n          <Stack mt={50} my=\"lg\">\n            {Object.keys(privacy).map((term, index) => (\n              <Box component=\"section\" mt=\"xl\" key={index}>\n                <Title order={2} mb=\"xl\" c=\"gray.8\">\n                  {term}\n                </Title>\n                {privacy[term].map(term => (\n                  <Text mt=\"md\" c=\"gray.8\" key={term} ml={term.startsWith(\"•\") ? 15 : 0}>\n                    {term}\n                  </Text>\n                ))}\n              </Box>\n            ))}\n          </Stack>\n        </Paper>\n      </Container>\n    </Layout>\n  );\n};\n\nexport default Privacy;\n"
  },
  {
    "path": "apps/www/src/pages/legal/terms.tsx",
    "content": "import React from \"react\";\nimport Head from \"next/head\";\nimport { Box, Container, Paper, Stack, Text, Title } from \"@mantine/core\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { SEO } from \"../../constants/seo\";\nimport terms from \"../../data/terms.json\";\nimport Layout from \"../../layout/PageLayout\";\n\nconst Terms = () => {\n  return (\n    <Layout>\n      <Head>\n        {generateNextSeo({\n          ...SEO,\n          title: \"Terms of Service - JSON Crack\",\n          description: \"JSON Crack Terms of Service\",\n          canonical: \"https://jsoncrack.com/legal/terms\",\n        })}\n      </Head>\n      <Container my={50} size=\"md\" pb=\"lg\">\n        <Paper bg=\"transparent\">\n          <Title ta=\"center\" c=\"gray.8\">\n            Terms of Service\n          </Title>\n          <Text c=\"gray.6\" ta=\"center\">\n            Last updated: No 30, 2024\n          </Text>\n\n          <Stack mt={50} my=\"lg\">\n            {Object.keys(terms).map((term, index) => (\n              <Box component=\"section\" mt=\"xl\" key={index}>\n                <Title order={2} mb=\"xl\" c=\"gray.8\">\n                  {term}\n                </Title>\n                {terms[term].map(term => (\n                  <Text mt=\"md\" c=\"gray.8\" key={term} ml={term.startsWith(\"•\") ? 15 : 0}>\n                    {term}\n                  </Text>\n                ))}\n              </Box>\n            ))}\n          </Stack>\n        </Paper>\n      </Container>\n    </Layout>\n  );\n};\n\nexport default Terms;\n"
  },
  {
    "path": "apps/www/src/pages/tools/json-schema.tsx",
    "content": "import React from \"react\";\nimport Head from \"next/head\";\nimport { Box, Button, Container, Flex, Paper, Title, Text } from \"@mantine/core\";\nimport { Editor, type OnMount } from \"@monaco-editor/react\";\nimport { generate } from \"json-schema-faker\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport { LuCheck, LuCircleX } from \"react-icons/lu\";\nimport { SEO } from \"../../constants/seo\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { editorOptions } from \"../../layout/ConverterLayout/options\";\nimport Layout from \"../../layout/PageLayout\";\nimport { generateType } from \"../../lib/utils/generateType\";\nimport { jsonToContent } from \"../../lib/utils/jsonAdapter\";\n\nconst JSONSchemaTool = () => {\n  const monacoRef = React.useRef<Parameters<OnMount>[1] | null>(null);\n  const [jsonError, setJsonError] = React.useState(false);\n  const [jsonSchemaError, setJsonSchemaError] = React.useState(false);\n  const [json, setJson] = React.useState(\"\");\n  const [jsonSchema, setJsonSchema] = React.useState(\"\");\n\n  React.useEffect(() => {\n    monacoRef.current?.languages.json.jsonDefaults.setDiagnosticsOptions({\n      validate: true,\n      allowComments: true,\n      enableSchemaRequest: true,\n      ...(jsonSchema && {\n        schemas: [\n          {\n            uri: \"\",\n            fileMatch: [\"*\"],\n            schema: jsonSchema,\n          },\n        ],\n      }),\n    });\n  }, [jsonSchema]);\n\n  const generateJsonSchema = async () => {\n    const jsonSchema = await generateType(json, FileFormat.JSON, TypeLanguage.JSON_SCHEMA);\n    setJsonSchema(jsonSchema);\n  };\n\n  const generateJson = async () => {\n    const randomJson = await generate(JSON.parse(jsonSchema));\n    const contents = await jsonToContent(JSON.stringify(randomJson, null, 2), FileFormat.JSON);\n    setJson(contents);\n  };\n\n  return (\n    <Layout>\n      <Head>\n        {generateNextSeo({\n          ...SEO,\n          title: \"JSON Schema Validator & Generator\",\n          description:\n            \"Use our JSON Schema Validator & Generator tool to easily validate and generate JSON schemas, and generate data from JSON schemas. Simply input your JSON data, generate the corresponding schema, and validate your data with ease.\",\n          canonical: \"https://jsoncrack.com/tools/json-schema\",\n        })}\n      </Head>\n      <Container mt=\"xl\" size=\"xl\">\n        <Title c=\"black\">JSON Schema Validator & Generator</Title>\n        <Flex pt=\"lg\" gap=\"lg\">\n          <Button\n            onClick={generateJsonSchema}\n            variant=\"default\"\n            size=\"md\"\n            disabled={!json.length || jsonError}\n          >\n            Generate JSON Schema\n          </Button>\n          <Button\n            onClick={generateJson}\n            variant=\"default\"\n            size=\"md\"\n            disabled={!jsonSchema.length || jsonSchemaError}\n          >\n            Generate JSON\n          </Button>\n        </Flex>\n        <Flex pt=\"lg\" gap=\"40\">\n          <Paper mah=\"600px\" withBorder flex=\"1\" style={{ overflow: \"hidden\" }}>\n            <Box p=\"xs\" bg=\"gray\">\n              <Flex justify=\"space-between\" align=\"center\">\n                <Text c=\"gray.3\">JSON</Text>\n                {jsonError ? <LuCircleX color=\"red\" /> : <LuCheck color=\"lightgreen\" />}\n              </Flex>\n            </Box>\n            <Editor\n              value={json}\n              onChange={value => setJson(value || \"\")}\n              onValidate={errors => setJsonError(!!errors.length)}\n              language=\"json\"\n              height={500}\n              options={editorOptions}\n              onMount={(_editor, monaco) => (monacoRef.current = monaco)}\n            />\n          </Paper>\n          <Paper mah=\"600px\" withBorder flex=\"1\" style={{ overflow: \"hidden\" }}>\n            <Box p=\"xs\" bg=\"gray\">\n              <Flex justify=\"space-between\" align=\"center\">\n                <Text c=\"gray.3\">JSON Schema</Text>\n                {jsonSchemaError ? <LuCircleX color=\"red\" /> : <LuCheck color=\"lightgreen\" />}\n              </Flex>\n            </Box>\n            <Editor\n              value={jsonSchema}\n              onChange={value => setJsonSchema(value || \"\")}\n              onValidate={errors => setJsonSchemaError(!!errors.length)}\n              language=\"json\"\n              height={500}\n              options={editorOptions}\n            />\n          </Paper>\n        </Flex>\n      </Container>\n    </Layout>\n  );\n};\n\nexport default JSONSchemaTool;\n"
  },
  {
    "path": "apps/www/src/pages/type/csv-to-go.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.CSV} to={TypeLanguage.Go} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/csv-to-kotlin.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.CSV} to={TypeLanguage.Kotlin} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/csv-to-rust.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.CSV} to={TypeLanguage.Rust} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/csv-to-typescript.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.CSV} to={TypeLanguage.TypeScript} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/json-to-go.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.JSON} to={TypeLanguage.Go} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/json-to-kotlin.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.JSON} to={TypeLanguage.Kotlin} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/json-to-rust.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.JSON} to={TypeLanguage.Rust} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/json-to-typescript.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.JSON} to={TypeLanguage.TypeScript} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/xml-to-go.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.XML} to={TypeLanguage.Go} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/xml-to-kotlin.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.XML} to={TypeLanguage.Kotlin} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/xml-to-rust.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.XML} to={TypeLanguage.Rust} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/xml-to-typescript.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.XML} to={TypeLanguage.TypeScript} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/yaml-to-go.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.YAML} to={TypeLanguage.Go} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/yaml-to-kotlin.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.YAML} to={TypeLanguage.Kotlin} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/yaml-to-rust.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.YAML} to={TypeLanguage.Rust} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/type/yaml-to-typescript.tsx",
    "content": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } from \"../../layout/TypeLayout/TypegenWrapper\";\n\nconst TypePage = () => {\n  return <TypegenWrapper from={FileFormat.YAML} to={TypeLanguage.TypeScript} />;\n};\n\nexport default TypePage;\n"
  },
  {
    "path": "apps/www/src/pages/widget.tsx",
    "content": "import React from \"react\";\nimport dynamic from \"next/dynamic\";\nimport Head from \"next/head\";\nimport { useRouter } from \"next/router\";\nimport { useMantineColorScheme } from \"@mantine/core\";\nimport { ThemeProvider } from \"styled-components\";\nimport type { LayoutDirection } from \"jsoncrack-react\";\nimport { generateNextSeo } from \"next-seo/pages\";\nimport toast from \"react-hot-toast\";\nimport { darkTheme, lightTheme } from \"../constants/theme\";\nimport useGraph from \"../features/editor/views/GraphView/stores/useGraph\";\nimport useFile from \"../store/useFile\";\nimport useJson from \"../store/useJson\";\n\ninterface EmbedMessage {\n  data: {\n    json?: string;\n    options?: {\n      theme?: \"light\" | \"dark\";\n      direction?: LayoutDirection;\n    };\n  };\n}\n\nconst ModalController = dynamic(() => import(\"../features/modals/ModalController\"), {\n  ssr: false,\n});\n\nconst GraphView = dynamic(\n  () => import(\"../features/editor/views/GraphView\").then(c => c.GraphView),\n  {\n    ssr: false,\n  }\n);\n\nconst WidgetPage = () => {\n  const { query, push, isReady } = useRouter();\n  const { setColorScheme } = useMantineColorScheme();\n  const [theme, setTheme] = React.useState<\"dark\" | \"light\">(\"dark\");\n  const checkEditorSession = useFile(state => state.checkEditorSession);\n  const setContents = useFile(state => state.setContents);\n  const setDirection = useGraph(state => state.setDirection);\n  const clearJson = useJson(state => state.clear);\n\n  React.useEffect(() => {\n    if (isReady) {\n      if (typeof query?.json === \"string\") checkEditorSession(query.json, true);\n      else clearJson();\n\n      window.parent.postMessage(window.frameElement?.getAttribute(\"id\"), \"*\");\n    }\n  }, [checkEditorSession, clearJson, isReady, push, query.json, query.partner]);\n\n  React.useEffect(() => {\n    const handler = (event: EmbedMessage) => {\n      try {\n        if (!event.data?.json) return;\n        if (event.data?.options?.theme === \"light\" || event.data?.options?.theme === \"dark\") {\n          setTheme(event.data.options.theme);\n        }\n\n        setContents({ contents: event.data.json, hasChanges: false });\n        setDirection(event.data.options?.direction || \"RIGHT\");\n      } catch (error) {\n        console.error(error);\n        toast.error(\"Invalid JSON!\");\n      }\n    };\n\n    window.addEventListener(\"message\", handler);\n    return () => window.removeEventListener(\"message\", handler);\n  }, [setColorScheme, setContents, setDirection, theme]);\n\n  React.useEffect(() => {\n    setColorScheme(theme);\n  }, [setColorScheme, theme]);\n\n  return (\n    <ThemeProvider theme={theme === \"dark\" ? darkTheme : lightTheme}>\n      <Head>{generateNextSeo({ noindex: true, nofollow: true })}</Head>\n      <ModalController />\n      <GraphView isWidget />\n    </ThemeProvider>\n  );\n};\n\nexport default WidgetPage;\n"
  },
  {
    "path": "apps/www/src/store/useConfig.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\n\nconst initialStates = {\n  darkmodeEnabled: true,\n  liveTransformEnabled: true,\n  gesturesEnabled: false,\n  rulersEnabled: true,\n};\n\nexport interface ConfigActions {\n  toggleDarkMode: (value: boolean) => void;\n  toggleLiveTransform: (value: boolean) => void;\n  toggleGestures: (value: boolean) => void;\n  toggleRulers: (value: boolean) => void;\n}\n\nconst useConfig = create(\n  persist<typeof initialStates & ConfigActions>(\n    set => ({\n      ...initialStates,\n      toggleRulers: rulersEnabled => set({ rulersEnabled }),\n      toggleGestures: gesturesEnabled => set({ gesturesEnabled }),\n      toggleLiveTransform: liveTransformEnabled => set({ liveTransformEnabled }),\n      toggleDarkMode: darkmodeEnabled => set({ darkmodeEnabled }),\n    }),\n    {\n      name: \"config\",\n    }\n  )\n);\n\nexport default useConfig;\n"
  },
  {
    "path": "apps/www/src/store/useFile.ts",
    "content": "import debounce from \"lodash.debounce\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { toast } from \"react-hot-toast\";\nimport { create } from \"zustand\";\nimport exampleJson from \"../data/example.json\";\nimport { FileFormat } from \"../enums/file.enum\";\nimport { isIframe } from \"../lib/utils/helpers\";\nimport { contentToJson, jsonToContent } from \"../lib/utils/jsonAdapter\";\nimport useConfig from \"./useConfig\";\nimport useJson from \"./useJson\";\n\nconst defaultJson = JSON.stringify(exampleJson, null, 2);\n\ntype SetContents = {\n  contents?: string;\n  hasChanges?: boolean;\n  skipUpdate?: boolean;\n  format?: FileFormat;\n};\n\ntype Query = string | string[] | undefined;\n\ninterface JsonActions {\n  getContents: () => string;\n  getFormat: () => FileFormat;\n  getHasChanges: () => boolean;\n  setError: (error: string | null) => void;\n  setHasChanges: (hasChanges: boolean) => void;\n  setContents: (data: SetContents) => void;\n  fetchUrl: (url: string) => void;\n  setFormat: (format: FileFormat) => void;\n  clear: () => void;\n  setFile: (fileData: File) => void;\n  setJsonSchema: (jsonSchema: object | null) => void;\n  checkEditorSession: (url: Query, widget?: boolean) => void;\n}\n\nexport type File = {\n  id: string;\n  views: number;\n  owner_email: string;\n  name: string;\n  content: string;\n  private: boolean;\n  format: FileFormat;\n  created_at: string;\n  updated_at: string;\n};\n\nconst initialStates = {\n  fileData: null as File | null,\n  format: FileFormat.JSON,\n  contents: defaultJson,\n  error: null as any,\n  hasChanges: false,\n  jsonSchema: null as object | null,\n};\n\nexport type FileStates = typeof initialStates;\n\nconst isURL = (value: string) => {\n  return /(https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})/gi.test(\n    value\n  );\n};\n\nconst debouncedUpdateJson = debounce((value: unknown) => {\n  useJson.getState().setJson(JSON.stringify(value, null, 2));\n}, 400);\n\nconst useFile = create<FileStates & JsonActions>()((set, get) => ({\n  ...initialStates,\n  clear: () => {\n    set({ contents: \"\" });\n    useJson.getState().clear();\n  },\n  setJsonSchema: jsonSchema => set({ jsonSchema }),\n  setFile: fileData => {\n    set({ fileData, format: fileData.format || FileFormat.JSON });\n    get().setContents({ contents: fileData.content, hasChanges: false });\n    gaEvent(\"set_content\", { label: fileData.format });\n  },\n  getContents: () => get().contents,\n  getFormat: () => get().format,\n  getHasChanges: () => get().hasChanges,\n  setFormat: async format => {\n    try {\n      const prevFormat = get().format;\n\n      set({ format });\n      const contentJson = await contentToJson(get().contents, prevFormat);\n      const jsonContent = await jsonToContent(JSON.stringify(contentJson, null, 2), format);\n\n      get().setContents({ contents: jsonContent });\n    } catch {\n      get().clear();\n      console.warn(\"The content was unable to be converted, so it was cleared instead.\");\n    }\n  },\n  setContents: async ({ contents, hasChanges = true, skipUpdate = false, format }) => {\n    try {\n      set({\n        ...(contents && { contents }),\n        error: null,\n        hasChanges,\n        format: format ?? get().format,\n      });\n\n      const isFetchURL = window.location.href.includes(\"?\");\n      const json = await contentToJson(get().contents, get().format);\n\n      if (!useConfig.getState().liveTransformEnabled && skipUpdate) return;\n\n      if (get().hasChanges && contents && contents.length < 80_000 && !isIframe() && !isFetchURL) {\n        sessionStorage.setItem(\"content\", contents);\n        sessionStorage.setItem(\"format\", get().format);\n        set({ hasChanges: true });\n      }\n\n      debouncedUpdateJson(json);\n    } catch (error: any) {\n      if (error?.mark?.snippet) return set({ error: error.mark.snippet });\n      if (error?.message) set({ error: error.message });\n      useJson.setState({ loading: false });\n    }\n  },\n  setError: error => set({ error }),\n  setHasChanges: hasChanges => set({ hasChanges }),\n  fetchUrl: async url => {\n    try {\n      const res = await fetch(url);\n      const json = await res.json();\n      const jsonStr = JSON.stringify(json, null, 2);\n\n      get().setContents({ contents: jsonStr });\n      return useJson.setState({ json: jsonStr, loading: false });\n    } catch {\n      get().clear();\n      toast.error(\"Failed to fetch document from URL!\");\n    }\n  },\n  checkEditorSession: (url, widget) => {\n    if (url && typeof url === \"string\" && isURL(url)) {\n      return get().fetchUrl(url);\n    }\n\n    let contents = defaultJson;\n    const sessionContent = sessionStorage.getItem(\"content\") as string | null;\n    const format = sessionStorage.getItem(\"format\") as FileFormat | null;\n    if (sessionContent && !widget) contents = sessionContent;\n\n    if (format) set({ format });\n    get().setContents({ contents, hasChanges: false });\n  },\n}));\n\nexport default useFile;\n"
  },
  {
    "path": "apps/www/src/store/useJson.ts",
    "content": "import { create } from \"zustand\";\n\ninterface JsonActions {\n  setJson: (json: string) => void;\n  getJson: () => string;\n  clear: () => void;\n}\n\nconst initialStates = {\n  json: \"{}\",\n  loading: true,\n};\n\nexport type JsonStates = typeof initialStates;\n\nconst useJson = create<JsonStates & JsonActions>()((set, get) => ({\n  ...initialStates,\n  getJson: () => get().json,\n  setJson: json => {\n    set({ json, loading: false });\n  },\n  clear: () => {\n    set({ json: \"\", loading: false });\n  },\n}));\n\nexport default useJson;\n"
  },
  {
    "path": "apps/www/src/store/useModal.ts",
    "content": "import { createWithEqualityFn as create } from \"zustand/traditional\";\nimport { type ModalName, modals } from \"../features/modals/modalTypes\";\n\ninterface ModalActions {\n  setVisible: (name: ModalName, open: boolean) => void;\n}\n\ntype ModalState = Record<ModalName, boolean>;\n\nconst initialStates: ModalState = modals.reduce((acc, modal) => {\n  acc[modal] = false;\n  return acc;\n}, {} as ModalState);\n\nexport const useModal = create<ModalState & ModalActions>()(set => ({\n  ...initialStates,\n  setVisible: (name, open) => {\n    set({ [name]: open });\n  },\n}));\n"
  },
  {
    "path": "apps/www/src/types/styled.d.ts",
    "content": "/* eslint-disable @typescript-eslint/no-empty-object-type */\nimport \"styled-components\";\nimport type theme from \"../constants/theme\";\n\ntype CustomTheme = typeof theme;\n\ndeclare module \"styled-components\" {\n  export interface DefaultTheme extends CustomTheme {}\n}\n"
  },
  {
    "path": "apps/www/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES6\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"noImplicitAny\": false,\n    \"typeRoots\": [\n      \"types\"\n    ]\n  },\n  \"include\": [\n    \"src\",\n    \"next-env.d.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"jsoncrack-monorepo\",\n  \"private\": true,\n  \"license\": \"Apache-2.0\",\n  \"homepage\": \"https://jsoncrack.com\",\n  \"author\": {\n    \"name\": \"Aykut Saraç\",\n    \"email\": \"aykutsarac0@gmail.com\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/AykutSarac/jsoncrack.com/issues\"\n  },\n  \"scripts\": {\n    \"dev\": \"turbo run dev\",\n    \"dev:www\": \"turbo run dev --filter=www\",\n    \"dev:vscode\": \"turbo run watch --filter=vscode\",\n    \"build\": \"turbo run build\",\n    \"build:www\": \"turbo run build --filter=www\",\n    \"build:vscode\": \"turbo run build --filter=vscode\",\n    \"start\": \"turbo run start\",\n    \"lint\": \"turbo run lint\",\n    \"lint:vscode\": \"turbo run lint --filter=vscode\",\n    \"lint:fix\": \"turbo run lint:fix\",\n    \"lint:fix:vscode\": \"turbo run lint:fix --filter=vscode\",\n    \"analyze\": \"turbo run analyze\",\n    \"clean\": \"turbo run clean\"\n  },\n  \"devDependencies\": {\n    \"turbo\": \"^2.8.17\"\n  },\n  \"engines\": {\n    \"node\": \">=24\",\n    \"pnpm\": \">=10\"\n  },\n  \"packageManager\": \"pnpm@10.20.0\",\n  \"pnpm\": {\n    \"onlyBuiltDependencies\": [\n      \"esbuild\",\n      \"sharp\",\n      \"unrs-resolver\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/.gitignore",
    "content": "node_modules\ndist/\n.turbo/\ncoverage/\n*.tsbuildinfo\n"
  },
  {
    "path": "packages/jsoncrack-react/.prettierrc",
    "content": "{\n  \"trailingComma\": \"es5\",\n  \"singleQuote\": false,\n  \"semi\": true,\n  \"printWidth\": 100,\n  \"arrowParens\": \"avoid\",\n  \"importOrder\": [\n    \"^(react/(.*)$)|^(react$)\",\n    \"<THIRD_PARTY_MODULES>\",\n    \"^[./]\"\n  ],\n  \"importOrderParserPlugins\": [\"typescript\", \"jsx\", \"decorators-legacy\"],\n  \"plugins\": [\"@trivago/prettier-plugin-sort-imports\"]\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/LICENSE.md",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2025 Aykut Saraç\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "packages/jsoncrack-react/README.md",
    "content": "# jsoncrack-react\n\nReusable JSON graph canvas component from [JSON Crack](https://jsoncrack.com) — visualize JSON as interactive node graphs.\n\n- React component API\n- Interactive canvas (pan/zoom + optional controls)\n- TypeScript types included\n\n[Live demo](https://jsoncrack.com) · [GitHub](https://github.com/AykutSarac/jsoncrack.com) · [npm](https://www.npmjs.com/package/jsoncrack-react)\n\n## Install\n\n```bash\nnpm install jsoncrack-react\n```\n\nPeer dependencies: `react >= 18`, `react-dom >= 18`\n\n## Setup\n\nImport the stylesheet once in your app entry point:\n\n```ts\nimport \"jsoncrack-react/style.css\";\n```\n\n## Quick Start\n\n```tsx\nimport { JSONCrack } from \"jsoncrack-react\";\n\nexport function Example() {\n  return (\n    <div style={{ height: 700 }}>\n      <JSONCrack\n        json={{\n          user: {\n            id: 1,\n            name: \"Ada\",\n            tags: [\"admin\", \"staff\"],\n          },\n        }}\n      />\n    </div>\n  );\n}\n```\n\nThe wrapper must have an explicit height.\n\n## Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `json` | `string \\| object \\| unknown[]` | - | JSON input data to visualize |\n| `theme` | `\"dark\" \\| \"light\"` | `\"dark\"` | Canvas theme |\n| `layoutDirection` | `\"LEFT\" \\| \"RIGHT\" \\| \"DOWN\" \\| \"UP\"` | `\"RIGHT\"` | Layout direction |\n| `showControls` | `boolean` | `true` | Show built-in camera controls |\n| `showGrid` | `boolean` | `true` | Show grid background |\n| `trackpadZoom` | `boolean` | `false` | Enables two-finger trackpad gesture zoom behavior |\n| `centerOnLayout` | `boolean` | `true` | Auto-center on first/major layout changes |\n| `maxRenderableNodes` | `number` | `1500` | Node rendering safety limit |\n| `className` | `string` | - | Wrapper class |\n| `style` | `React.CSSProperties` | - | Wrapper inline style |\n| `onNodeClick` | `(node: NodeData) => void` | - | Node click callback |\n| `onParse` | `(graph: GraphData) => void` | - | Parsed graph callback |\n| `onParseError` | `(error: Error) => void` | - | Parse error callback |\n| `onViewportCreate` | `(viewPort: ViewPort) => void` | - | Viewport ready callback |\n| `renderNodeLimitExceeded` | `(nodeCount: number, maxRenderableNodes: number) => React.ReactNode` | - | Custom fallback when node limit is exceeded |\n\n## Performance\n\nThe component renders all nodes as SVG elements. For large inputs, rendering cost grows with the number of nodes.\n\n- **Default limit:** `maxRenderableNodes` is set to `1500`. Graphs exceeding this render a fallback instead of the canvas.\n- **Recommended range:** Up to ~300–500 nodes for smooth interaction. Beyond that, panning and zooming may feel sluggish depending on the device.\n- **Reduce node count:** Flatten or trim your data before passing it in. Arrays of primitives become individual nodes, so large arrays expand the graph quickly.\n- **Custom fallback:** Use `renderNodeLimitExceeded` to show a message or alternative UI when the limit is hit.\n\n```tsx\n<JSONCrack\n  json={data}\n  maxRenderableNodes={300}\n  renderNodeLimitExceeded={(count, max) => (\n    <p>Too large to render ({count} nodes, limit is {max})</p>\n  )}\n/>\n```\n\n## Imperative API (ref)\n\n```tsx\nimport { useRef } from \"react\";\nimport { JSONCrack, type JSONCrackRef } from \"jsoncrack-react\";\n\nexport function WithRef({ json }: { json: string }) {\n  const ref = useRef<JSONCrackRef>(null);\n\n  return (\n    <>\n      <button onClick={() => ref.current?.centerView()}>Center</button>\n      <button onClick={() => ref.current?.zoomIn()}>+</button>\n      <button onClick={() => ref.current?.zoomOut()}>-</button>\n      <div style={{ height: 600 }}>\n        <JSONCrack ref={ref} json={json} />\n      </div>\n    </>\n  );\n}\n```\n\n`JSONCrackRef` methods:\n\n- `zoomIn()`\n- `zoomOut()`\n- `setZoom(zoomFactor: number)`\n- `centerView()`\n- `focusFirstNode()`\n\n## Utility: `parseGraph`\n\nIf you only need parser output:\n\n```ts\nimport { parseGraph } from \"jsoncrack-react\";\n\nconst result = parseGraph('{\"a\":[1,2,3]}');\n// result: { nodes, edges, errors }\n```\n\n## Exported Types\n\n- `JSONCrackProps`\n- `JSONCrackRef`\n- `ParseGraphResult`\n- `CanvasThemeMode`\n- `LayoutDirection`\n- `NodeData`\n- `EdgeData`\n- `GraphData`\n- `NodeRow`\n\n## License\n\nApache-2.0\n"
  },
  {
    "path": "packages/jsoncrack-react/eslint.config.mjs",
    "content": "import eslint from \"@eslint/js\";\nimport eslintConfigPrettier from \"eslint-config-prettier/flat\";\nimport eslintPluginPrettier from \"eslint-plugin-prettier/recommended\";\nimport unusedImports from \"eslint-plugin-unused-imports\";\nimport { defineConfig, globalIgnores } from \"eslint/config\";\nimport tseslint from \"typescript-eslint\";\n\nexport default defineConfig([\n  globalIgnores([\"dist\", \"scripts\", \"node_modules\"]),\n  eslint.configs.recommended,\n  tseslint.configs.recommended,\n  eslintConfigPrettier,\n  eslintPluginPrettier,\n  {\n    languageOptions: {\n      parserOptions: {\n        tsconfigRootDir: import.meta.dirname,\n      },\n    },\n    plugins: {\n      \"unused-imports\": unusedImports,\n    },\n    rules: {\n      \"@typescript-eslint/consistent-type-imports\": \"error\",\n      \"unused-imports/no-unused-imports\": \"error\",\n      \"@typescript-eslint/no-explicit-any\": \"off\",\n      \"prettier/prettier\": \"error\",\n      \"space-in-parens\": \"error\",\n      \"no-empty\": \"error\",\n      \"no-multiple-empty-lines\": \"error\",\n      \"no-irregular-whitespace\": \"error\",\n      strict: [\"error\", \"never\"],\n      \"linebreak-style\": [\"error\", \"unix\"],\n      quotes: [\"error\", \"double\", { avoidEscape: true }],\n      semi: [\"error\", \"always\"],\n      \"prefer-const\": \"error\",\n      \"space-before-function-paren\": [\n        \"error\",\n        {\n          anonymous: \"always\",\n          named: \"never\",\n          asyncArrow: \"always\",\n        },\n      ],\n    },\n  },\n]);\n"
  },
  {
    "path": "packages/jsoncrack-react/package.json",
    "content": "{\n  \"name\": \"jsoncrack-react\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Reusable JSON Crack canvas as a React component\",\n  \"license\": \"Apache-2.0\",\n  \"author\": \"Aykut Saraç\",\n  \"homepage\": \"https://jsoncrack.com\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/AykutSarac/jsoncrack.com\"\n  },\n  \"keywords\": [\n    \"json\",\n    \"visualizer\",\n    \"graph\",\n    \"canvas\",\n    \"react\",\n    \"json-crack\",\n    \"typescript\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\"\n    },\n    \"./style.css\": \"./dist/index.css\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": [\n    \"./dist/index.css\"\n  ],\n  \"scripts\": {\n    \"build\": \"vite build && tsc -p tsconfig.build.json\",\n    \"clean\": \"rm -rf dist\",\n    \"lint\": \"tsc -p tsconfig.build.json --noEmit && eslint src && prettier --check src\",\n    \"lint:fix\": \"eslint --fix src && prettier --write src\"\n  },\n  \"peerDependencies\": {\n    \"react\": \">=18\",\n    \"react-dom\": \">=18\"\n  },\n  \"dependencies\": {\n    \"jsonc-parser\": \"^3.3.1\",\n    \"react-zoomable-ui\": \"^0.11.0\",\n    \"reaflow\": \"5.4.1\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^10.0.1\",\n    \"@trivago/prettier-plugin-sort-imports\": \"^6.0.2\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.57.1\",\n    \"@typescript-eslint/parser\": \"^8.57.1\",\n    \"@vitejs/plugin-react\": \"^6.0.1\",\n    \"eslint\": \"^10.0.3\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"eslint-plugin-unused-imports\": \"^4.4.1\",\n    \"prettier\": \"^3.8.1\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.57.1\",\n    \"vite\": \"^8.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/src/JSONCrackComponent.tsx",
    "content": "\"use client\";\n\nimport React from \"react\";\nimport type { ViewPort } from \"react-zoomable-ui\";\nimport { Space } from \"react-zoomable-ui\";\nimport { Canvas } from \"reaflow\";\nimport type { ElkRoot } from \"reaflow\";\nimport styles from \"./JSONCrackStyles.module.css\";\nimport { Controls } from \"./components/Controls\";\nimport { CustomEdge } from \"./components/CustomEdge\";\nimport { CustomNode } from \"./components/CustomNode\";\nimport { parseGraph } from \"./parser\";\nimport { themes } from \"./theme\";\nimport type { CanvasThemeMode, GraphData, LayoutDirection, NodeData } from \"./types\";\n\nconst layoutOptions = {\n  \"elk.layered.compaction.postCompaction.strategy\": \"EDGE_LENGTH\",\n  \"elk.layered.nodePlacement.strategy\": \"NETWORK_SIMPLEX\",\n  \"elk.spacing.edgeLabel\": \"15\",\n};\n\nconst objectJsonCache = new WeakMap<object, string>();\n\nexport interface JSONCrackRef {\n  zoomIn: () => void;\n  zoomOut: () => void;\n  setZoom: (zoomFactor: number) => void;\n  centerView: () => void;\n  focusFirstNode: () => void;\n}\n\nexport interface JSONCrackProps {\n  json: string | object | unknown[];\n  theme?: CanvasThemeMode;\n  layoutDirection?: LayoutDirection;\n  showControls?: boolean;\n  showGrid?: boolean;\n  trackpadZoom?: boolean;\n  centerOnLayout?: boolean;\n  maxRenderableNodes?: number;\n  className?: string;\n  style?: React.CSSProperties;\n  onNodeClick?: (node: NodeData) => void;\n  onParse?: (graph: GraphData) => void;\n  onParseError?: (error: Error) => void;\n  onViewportCreate?: (viewPort: ViewPort) => void;\n  renderNodeLimitExceeded?: (nodeCount: number, maxRenderableNodes: number) => React.ReactNode;\n}\n\nconst toJsonText = (json: JSONCrackProps[\"json\"]): string => {\n  if (typeof json === \"string\") return json;\n\n  if (json && typeof json === \"object\") {\n    const cached = objectJsonCache.get(json);\n    if (cached) return cached;\n\n    const serialized = JSON.stringify(json, null, 2);\n    objectJsonCache.set(json, serialized);\n    return serialized;\n  }\n\n  return JSON.stringify(json, null, 2);\n};\n\nexport const JSONCrack = React.forwardRef<JSONCrackRef, JSONCrackProps>(\n  (\n    {\n      json,\n      theme = \"dark\",\n      layoutDirection = \"RIGHT\",\n      showControls = true,\n      showGrid = true,\n      trackpadZoom = false,\n      centerOnLayout = true,\n      maxRenderableNodes = 1500,\n      className,\n      style,\n      onNodeClick,\n      onParse,\n      onParseError,\n      onViewportCreate,\n      renderNodeLimitExceeded,\n    },\n    ref\n  ) => {\n    const themeTokens = themes[theme];\n    const containerRef = React.useRef<HTMLDivElement | null>(null);\n    const [viewPort, setViewPort] = React.useState<ViewPort | null>(null);\n    const [nodes, setNodes] = React.useState<GraphData[\"nodes\"]>([]);\n    const [edges, setEdges] = React.useState<GraphData[\"edges\"]>([]);\n    const [loading, setLoading] = React.useState(true);\n    const [aboveSupportedLimit, setAboveSupportedLimit] = React.useState(false);\n    const [totalNodes, setTotalNodes] = React.useState(0);\n    const [paneWidth, setPaneWidth] = React.useState(2000);\n    const [paneHeight, setPaneHeight] = React.useState(2000);\n    const hasAutoFittedRef = React.useRef(false);\n    const previousLayoutAreaRef = React.useRef<number | null>(null);\n    const callbacksRef = React.useRef({ onParse, onParseError });\n    const onViewportCreateRef = React.useRef(onViewportCreate);\n    const lastParsedInputRef = React.useRef<{\n      jsonText: string;\n      maxRenderableNodes: number;\n    } | null>(null);\n\n    React.useEffect(() => {\n      callbacksRef.current = { onParse, onParseError };\n    }, [onParse, onParseError]);\n\n    React.useEffect(() => {\n      onViewportCreateRef.current = onViewportCreate;\n    }, [onViewportCreate]);\n\n    React.useEffect(() => {\n      hasAutoFittedRef.current = false;\n      previousLayoutAreaRef.current = null;\n    }, [layoutDirection]);\n\n    const centerView = React.useCallback(() => {\n      const nextViewPort = viewPort;\n      nextViewPort?.updateContainerSize();\n\n      const canvas = containerRef.current?.querySelector(\".jsoncrack-canvas\") as HTMLElement | null;\n      if (canvas) {\n        nextViewPort?.camera?.centerFitElementIntoView(canvas);\n      }\n    }, [viewPort]);\n\n    const focusFirstNode = React.useCallback(() => {\n      const rootNode = containerRef.current?.querySelector(\"g[id$='node-1']\") as HTMLElement | null;\n      if (!rootNode) return;\n\n      viewPort?.camera?.centerFitElementIntoView(rootNode, {\n        elementExtraMarginForZoom: 100,\n      });\n    }, [viewPort]);\n\n    const setZoom = React.useCallback(\n      (zoomFactor: number) => {\n        if (!viewPort) return;\n        viewPort.camera?.recenter(viewPort.centerX, viewPort.centerY, zoomFactor);\n      },\n      [viewPort]\n    );\n\n    const zoomIn = React.useCallback(() => {\n      if (!viewPort) return;\n      viewPort.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor + 0.1);\n    }, [viewPort]);\n\n    const zoomOut = React.useCallback(() => {\n      if (!viewPort) return;\n      viewPort.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor - 0.1);\n    }, [viewPort]);\n\n    React.useImperativeHandle(\n      ref,\n      () => ({\n        zoomIn,\n        zoomOut,\n        setZoom,\n        centerView,\n        focusFirstNode,\n      }),\n      [centerView, focusFirstNode, setZoom, zoomIn, zoomOut]\n    );\n\n    React.useEffect(() => {\n      try {\n        const jsonText = toJsonText(json);\n        const lastParsedInput = lastParsedInputRef.current;\n\n        if (\n          lastParsedInput &&\n          lastParsedInput.jsonText === jsonText &&\n          lastParsedInput.maxRenderableNodes === maxRenderableNodes\n        ) {\n          return;\n        }\n\n        setLoading(true);\n\n        const graph = parseGraph(jsonText);\n\n        if (graph.errors.length > 0) {\n          callbacksRef.current.onParseError?.(\n            new Error(`Failed to parse data (${graph.errors.length} syntax error(s)).`)\n          );\n        }\n\n        setTotalNodes(graph.nodes.length);\n\n        if (graph.nodes.length > maxRenderableNodes) {\n          setAboveSupportedLimit(true);\n          setNodes([]);\n          setEdges([]);\n          setLoading(false);\n          lastParsedInputRef.current = {\n            jsonText,\n            maxRenderableNodes,\n          };\n          return;\n        }\n\n        setAboveSupportedLimit(false);\n        setNodes(graph.nodes);\n        setEdges(graph.edges);\n        callbacksRef.current.onParse?.({\n          nodes: graph.nodes,\n          edges: graph.edges,\n        });\n        lastParsedInputRef.current = {\n          jsonText,\n          maxRenderableNodes,\n        };\n\n        if (graph.nodes.length === 0) {\n          setLoading(false);\n        }\n      } catch (error) {\n        setNodes([]);\n        setEdges([]);\n        setLoading(false);\n        callbacksRef.current.onParseError?.(\n          error instanceof Error ? error : new Error(\"Unable to parse data.\")\n        );\n      }\n    }, [json, maxRenderableNodes]);\n\n    const edgeTargetById = React.useMemo(() => {\n      const targetById = new Map<string, string>();\n\n      for (let i = 0; i < edges.length; i += 1) {\n        const edge = edges[i];\n        targetById.set(edge.id, edge.to);\n      }\n\n      return targetById;\n    }, [edges]);\n\n    const onLayoutChange = React.useCallback(\n      (layout: ElkRoot) => {\n        if (!layout.width || !layout.height) {\n          setLoading(false);\n          return;\n        }\n\n        const currentLayoutArea = layout.width * layout.height;\n        const previousLayoutArea = previousLayoutAreaRef.current;\n        previousLayoutAreaRef.current = currentLayoutArea;\n\n        setPaneWidth(layout.width + 50);\n        setPaneHeight(layout.height + 50);\n\n        setTimeout(() => {\n          window.requestAnimationFrame(() => {\n            const isFirstAutoFit = !hasAutoFittedRef.current;\n            const hasLargeLayoutChange =\n              previousLayoutArea !== null &&\n              previousLayoutArea > 0 &&\n              Math.abs((currentLayoutArea * 100) / previousLayoutArea - 100) > 70;\n            const shouldAutoFit = centerOnLayout && (isFirstAutoFit || hasLargeLayoutChange);\n\n            if (shouldAutoFit) {\n              centerView();\n              hasAutoFittedRef.current = true;\n            }\n\n            setLoading(false);\n          });\n        }, 0);\n      },\n      [centerView, centerOnLayout]\n    );\n\n    const tooLargeContent = renderNodeLimitExceeded?.(totalNodes, maxRenderableNodes);\n    const canvasClassName = [styles.canvasWrapper, showGrid ? styles.showGrid : \"\", className]\n      .filter(Boolean)\n      .join(\" \");\n    const canvasStyle = {\n      \"--bg-color\": themeTokens.GRID_BG_COLOR,\n      \"--line-color-1\": themeTokens.GRID_COLOR_PRIMARY,\n      \"--line-color-2\": themeTokens.GRID_COLOR_SECONDARY,\n      \"--edge-stroke\": theme === \"dark\" ? \"#444444\" : \"#BCBEC0\",\n      \"--node-fill\": theme === \"dark\" ? \"#292929\" : \"#ffffff\",\n      \"--node-stroke\": theme === \"dark\" ? \"#424242\" : \"#BCBEC0\",\n      \"--interactive-normal\": themeTokens.INTERACTIVE_NORMAL,\n      \"--background-node\": themeTokens.BACKGROUND_NODE,\n      \"--node-text\": themeTokens.NODE_COLORS.TEXT,\n      \"--node-key\": themeTokens.NODE_COLORS.NODE_KEY,\n      \"--node-value\": themeTokens.NODE_COLORS.NODE_VALUE,\n      \"--node-integer\": themeTokens.NODE_COLORS.INTEGER,\n      \"--node-null\": themeTokens.NODE_COLORS.NULL,\n      \"--node-bool-true\": themeTokens.NODE_COLORS.BOOL.TRUE,\n      \"--node-bool-false\": themeTokens.NODE_COLORS.BOOL.FALSE,\n      \"--node-child-count\": themeTokens.NODE_COLORS.CHILD_COUNT,\n      \"--node-divider\": themeTokens.NODE_COLORS.DIVIDER,\n      \"--text-positive\": themeTokens.TEXT_POSITIVE,\n      \"--background-modifier-accent\": themeTokens.BACKGROUND_MODIFIER_ACCENT,\n      \"--spinner-track\": theme === \"dark\" ? \"rgba(255, 255, 255, 0.3)\" : \"rgba(17, 24, 39, 0.2)\",\n      \"--spinner-head\": theme === \"dark\" ? \"#FFFFFF\" : \"#111827\",\n      \"--overlay-bg\": theme === \"dark\" ? \"rgba(0, 0, 0, 0.2)\" : \"rgba(255, 255, 255, 0.38)\",\n      ...style,\n    } as React.CSSProperties;\n\n    return (\n      <div\n        ref={containerRef}\n        className={canvasClassName}\n        style={canvasStyle}\n        onContextMenu={event => event.preventDefault()}\n      >\n        {showControls && (\n          <Controls\n            onFocusRoot={focusFirstNode}\n            onCenterView={centerView}\n            onZoomOut={zoomOut}\n            onZoomIn={zoomIn}\n          />\n        )}\n\n        {aboveSupportedLimit &&\n          (tooLargeContent ? (\n            tooLargeContent\n          ) : (\n            <div className={styles.tooLarge}>\n              {`This graph has ${totalNodes} nodes and exceeds the maxRenderableNodes limit (${maxRenderableNodes}).`}\n            </div>\n          ))}\n\n        {loading && (\n          <div className={styles.overlay}>\n            <div className={styles.spinner} />\n          </div>\n        )}\n\n        <Space\n          onCreate={nextViewPort => {\n            setViewPort(nextViewPort);\n            onViewportCreateRef.current?.(nextViewPort);\n          }}\n          onContextMenu={event => event.preventDefault()}\n          treatTwoFingerTrackPadGesturesLikeTouch={trackpadZoom}\n          pollForElementResizing\n          className=\"jsoncrack-space\"\n        >\n          <Canvas\n            className=\"jsoncrack-canvas\"\n            onLayoutChange={onLayoutChange}\n            node={nodeProps => <CustomNode {...nodeProps} onNodeClick={onNodeClick} />}\n            edge={edgeProps => (\n              <CustomEdge\n                {...edgeProps}\n                viewPort={viewPort}\n                edgeTargetById={edgeTargetById}\n                hostElement={containerRef.current}\n              />\n            )}\n            nodes={nodes}\n            edges={edges}\n            arrow={null}\n            maxHeight={paneHeight}\n            maxWidth={paneWidth}\n            height={paneHeight}\n            width={paneWidth}\n            direction={layoutDirection}\n            layoutOptions={layoutOptions}\n            key={layoutDirection}\n            pannable={false}\n            zoomable={false}\n            animated={false}\n            readonly\n            dragEdge={null}\n            dragNode={null}\n            fit\n          />\n        </Space>\n      </div>\n    );\n  }\n);\n\nJSONCrack.displayName = \"JSONCrack\";\n"
  },
  {
    "path": "packages/jsoncrack-react/src/JSONCrackStyles.module.css",
    "content": ".canvasWrapper {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  background-color: var(--bg-color);\n}\n\n.showGrid {\n  background-image:\n    linear-gradient(var(--line-color-1) 1.5px, transparent 1.5px),\n    linear-gradient(90deg, var(--line-color-1) 1.5px, transparent 1.5px),\n    linear-gradient(var(--line-color-2) 1px, transparent 1px),\n    linear-gradient(90deg, var(--line-color-2) 1px, transparent 1px);\n  background-position:\n    -1.5px -1.5px,\n    -1.5px -1.5px,\n    -1px -1px,\n    -1px -1px;\n  background-size:\n    100px 100px,\n    100px 100px,\n    20px 20px,\n    20px 20px;\n}\n\n.canvasWrapper :global(.jsoncrack-space) {\n  cursor: grab;\n}\n\n.canvasWrapper :global(.jsoncrack-space:active) {\n  cursor: grabbing;\n}\n\n.canvasWrapper :global(.dragging),\n.canvasWrapper :global(.dragging button) {\n  pointer-events: none;\n}\n\n.canvasWrapper :global(text) {\n  fill: var(--interactive-normal) !important;\n}\n\n.canvasWrapper :global(rect) {\n  fill: var(--node-fill);\n}\n\n.overlay {\n  position: absolute;\n  inset: 0;\n  z-index: 30;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  pointer-events: none;\n  background: radial-gradient(circle at center, var(--overlay-bg) 0 44px, transparent 96px);\n}\n\n.spinner {\n  width: 30px;\n  height: 30px;\n  border-radius: 9999px;\n  border: 3px solid var(--spinner-track);\n  border-top-color: var(--spinner-head);\n  border-right-color: var(--spinner-head);\n  animation: spin 0.9s linear infinite;\n}\n\n@keyframes spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.tooLarge {\n  position: absolute;\n  inset: 0;\n  z-index: 40;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 24px;\n  text-align: center;\n  background: rgba(0, 0, 0, 0.62);\n  color: #ffffff;\n  font-size: 14px;\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/Controls.module.css",
    "content": ".controls {\n  position: absolute;\n  bottom: 10px;\n  left: 10px;\n  z-index: 100;\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  background: rgba(20, 20, 20, 0.38);\n  border: 1px solid rgba(255, 255, 255, 0.18);\n  border-radius: 8px;\n  padding: 6px;\n  backdrop-filter: blur(8px);\n}\n\n.button {\n  border: none;\n  border-radius: 6px;\n  cursor: pointer;\n  min-width: 30px;\n  height: 30px;\n  padding: 0 8px;\n  font-size: 12px;\n  color: #ffffff;\n  background: rgba(255, 255, 255, 0.18);\n}\n\n.button:hover {\n  background: rgba(255, 255, 255, 0.28);\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/Controls.tsx",
    "content": "import styles from \"./Controls.module.css\";\n\ninterface ControlsProps {\n  onFocusRoot: () => void;\n  onCenterView: () => void;\n  onZoomOut: () => void;\n  onZoomIn: () => void;\n}\n\nexport const Controls = ({ onFocusRoot, onCenterView, onZoomOut, onZoomIn }: ControlsProps) => {\n  return (\n    <div className={styles.controls}>\n      <button\n        className={styles.button}\n        type=\"button\"\n        onClick={onFocusRoot}\n        title=\"Center first node\"\n      >\n        Root\n      </button>\n      <button className={styles.button} type=\"button\" onClick={onCenterView} title=\"Fit view\">\n        Fit\n      </button>\n      <button className={styles.button} type=\"button\" onClick={onZoomOut} title=\"Zoom out\">\n        -\n      </button>\n      <button className={styles.button} type=\"button\" onClick={onZoomIn} title=\"Zoom in\">\n        +\n      </button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/CustomEdge.tsx",
    "content": "import React from \"react\";\nimport type { ViewPort } from \"react-zoomable-ui\";\nimport type { EdgeProps } from \"reaflow\";\nimport { Edge } from \"reaflow\";\nimport type { EdgeData } from \"../types\";\n\ntype QueryRoot = {\n  querySelector: (selector: string) => Element | null;\n};\n\ntype CustomEdgeProps = EdgeProps & {\n  viewPort: ViewPort | null;\n  edgeTargetById: Map<string, string>;\n  hostElement: QueryRoot | null;\n};\n\nconst isQueryRoot = (value: unknown): value is QueryRoot => {\n  return (\n    typeof value === \"object\" &&\n    value !== null &&\n    \"querySelector\" in value &&\n    typeof (value as QueryRoot).querySelector === \"function\"\n  );\n};\n\nconst CustomEdgeBase = ({ viewPort, edgeTargetById, hostElement, ...props }: CustomEdgeProps) => {\n  const [hovered, setHovered] = React.useState(false);\n  const edgeId = (props.properties as EdgeData | undefined)?.id;\n\n  const handleClick = React.useCallback(() => {\n    const targetNodeId = edgeId ? edgeTargetById.get(edgeId) : undefined;\n    if (!targetNodeId) return;\n\n    const queryRoot = isQueryRoot(hostElement)\n      ? hostElement\n      : typeof document !== \"undefined\"\n        ? document\n        : null;\n    if (!queryRoot) return;\n\n    const targetNodeDom = queryRoot.querySelector(\n      `[data-id$=\"node-${targetNodeId}\"]`\n    ) as HTMLElement | null;\n\n    if (targetNodeDom?.parentElement) {\n      viewPort?.camera.centerFitElementIntoView(targetNodeDom.parentElement, {\n        elementExtraMarginForZoom: 150,\n      });\n    }\n  }, [hostElement, edgeId, edgeTargetById, viewPort]);\n\n  return (\n    <Edge\n      containerClassName={`edge-${props.id}`}\n      onClick={handleClick}\n      onEnter={() => setHovered(true)}\n      onLeave={() => setHovered(false)}\n      style={{\n        stroke: hovered ? \"#3B82F6\" : \"var(--edge-stroke)\",\n        strokeWidth: 1.5,\n      }}\n      {...props}\n    />\n  );\n};\n\nexport const CustomEdge = React.memo(CustomEdgeBase);\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/CustomNode.tsx",
    "content": "import React from \"react\";\nimport type { NodeProps } from \"reaflow\";\nimport { Node } from \"reaflow\";\nimport type { NodeData } from \"../types\";\nimport { ObjectNode } from \"./ObjectNode\";\nimport { TextNode } from \"./TextNode\";\n\ntype CustomNodeProps = NodeProps<NodeData> & {\n  onNodeClick?: (node: NodeData) => void;\n};\n\nconst CustomNodeBase = ({ onNodeClick, ...nodeProps }: CustomNodeProps) => {\n  const handleNodeClick = React.useCallback(\n    (_: React.MouseEvent<SVGGElement, MouseEvent>, data: NodeData) => {\n      onNodeClick?.(data);\n    },\n    [onNodeClick]\n  );\n\n  return (\n    <Node\n      {...nodeProps}\n      onClick={handleNodeClick as any}\n      animated={false}\n      label={null as any}\n      onEnter={event => {\n        event.currentTarget.style.stroke = \"#3B82F6\";\n      }}\n      onLeave={event => {\n        event.currentTarget.style.stroke = \"var(--node-stroke)\";\n      }}\n      style={{\n        fill: \"var(--node-fill)\",\n        stroke: \"var(--node-stroke)\",\n        strokeWidth: 1,\n      }}\n    >\n      {({ node, x, y }) => {\n        const hasKey = nodeProps.properties.text[0]?.key;\n        if (!hasKey) {\n          return <TextNode node={nodeProps.properties as NodeData} x={x} y={y} />;\n        }\n\n        return <ObjectNode node={node as NodeData} x={x} y={y} />;\n      }}\n    </Node>\n  );\n};\n\nexport const CustomNode = React.memo(CustomNodeBase);\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/Node.module.css",
    "content": ".foreignObject {\n  text-align: center;\n  color: var(--node-text);\n  font-family:\n    ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\",\n    monospace;\n  font-size: 12px;\n  font-weight: 500;\n  overflow: hidden;\n  pointer-events: none;\n}\n\n.objectForeignObject {\n  text-align: left;\n}\n\n.key {\n  display: inline;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.row {\n  padding: 3px 10px;\n  height: 30px;\n  line-height: 24px;\n  display: block;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  border-bottom: 1px solid var(--node-divider);\n  box-sizing: border-box;\n}\n\n.row:last-of-type {\n  border-bottom: none;\n}\n\n.textNodeWrapper {\n  display: flex;\n  justify-content: flex-start;\n  align-items: center;\n  height: 100%;\n  width: 100%;\n  overflow: hidden;\n  padding: 0 10px;\n}\n\n.foreignObject:global(.searched) {\n  background: rgba(27, 255, 0, 0.1);\n  border: 1px solid var(--text-positive);\n  border-radius: 2px;\n  box-sizing: border-box;\n}\n\n.foreignObject :global(.highlight) {\n  background: rgba(255, 214, 0, 0.15);\n}\n\n.foreignObject:global(.searched) .row {\n  border-bottom: 1px solid var(--text-positive);\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/ObjectNode.tsx",
    "content": "import React from \"react\";\nimport type { NodeData } from \"../types\";\nimport styles from \"./Node.module.css\";\nimport { TextRenderer } from \"./TextRenderer\";\nimport { getTextColor } from \"./nodeStyles\";\n\ntype ObjectNodeProps = {\n  node: NodeData;\n  x: number;\n  y: number;\n};\n\ntype RowProps = {\n  row: NodeData[\"text\"][number];\n  x: number;\n  y: number;\n  index: number;\n};\n\nconst ROW_HEIGHT = 30;\n\nconst Row = ({ row, x, y, index }: RowProps) => {\n  const rowPosition = index * ROW_HEIGHT;\n\n  const getRowText = () => {\n    if (row.type === \"object\") return `{${row.childrenCount ?? 0} keys}`;\n    if (row.type === \"array\") return `[${row.childrenCount ?? 0} items]`;\n    return row.value;\n  };\n\n  return (\n    <span\n      className={styles.row}\n      style={{\n        color: getTextColor({ value: row.value, type: typeof row.value }),\n      }}\n      data-key={`${row.key}: ${row.value}`}\n      data-x={x}\n      data-y={y + rowPosition}\n    >\n      <span\n        className={styles.key}\n        style={{ color: getTextColor({ type: \"object\", value: row.value }) }}\n      >\n        {row.key}:{\" \"}\n      </span>\n      <TextRenderer>{getRowText()}</TextRenderer>\n    </span>\n  );\n};\n\nconst ObjectNodeBase = ({ node, x, y }: ObjectNodeProps) => (\n  <foreignObject\n    className={`${styles.foreignObject} ${styles.objectForeignObject}`}\n    data-id={`node-${node.id}`}\n    width={node.width}\n    height={node.height}\n    x={0}\n    y={0}\n  >\n    {node.text.map((row, index) => (\n      <Row key={`${node.id}-${index}`} row={row} x={x} y={y} index={index} />\n    ))}\n  </foreignObject>\n);\n\nconst areRowTargetsEqual = (prevTargets?: string[], nextTargets?: string[]) => {\n  if (prevTargets === nextTargets) return true;\n  if (!prevTargets || !nextTargets) return false;\n  if (prevTargets.length !== nextTargets.length) return false;\n\n  for (let i = 0; i < prevTargets.length; i += 1) {\n    if (prevTargets[i] !== nextTargets[i]) return false;\n  }\n\n  return true;\n};\n\nconst areRowsEqual = (prevRows: NodeData[\"text\"], nextRows: NodeData[\"text\"]) => {\n  if (prevRows === nextRows) return true;\n  if (prevRows.length !== nextRows.length) return false;\n\n  for (let i = 0; i < prevRows.length; i += 1) {\n    const prevRow = prevRows[i];\n    const nextRow = nextRows[i];\n\n    if (\n      prevRow.key !== nextRow.key ||\n      prevRow.value !== nextRow.value ||\n      prevRow.type !== nextRow.type ||\n      prevRow.childrenCount !== nextRow.childrenCount ||\n      !areRowTargetsEqual(prevRow.to, nextRow.to)\n    ) {\n      return false;\n    }\n  }\n\n  return true;\n};\n\nconst propsAreEqual = (prev: ObjectNodeProps, next: ObjectNodeProps) => {\n  return (\n    prev.node.width === next.node.width &&\n    prev.node.height === next.node.height &&\n    areRowsEqual(prev.node.text, next.node.text)\n  );\n};\n\nexport const ObjectNode = React.memo(ObjectNodeBase, propsAreEqual);\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/TextNode.tsx",
    "content": "import React from \"react\";\nimport type { NodeData } from \"../types\";\nimport styles from \"./Node.module.css\";\nimport { TextRenderer } from \"./TextRenderer\";\nimport { getTextColor } from \"./nodeStyles\";\n\ntype TextNodeProps = {\n  node: NodeData;\n  x: number;\n  y: number;\n};\n\nconst TextNodeBase = ({ node, x, y }: TextNodeProps) => {\n  const { text, width, height } = node;\n  const firstRow = text[0];\n\n  if (!firstRow) return null;\n\n  const value = firstRow.value;\n\n  return (\n    <foreignObject\n      className={styles.foreignObject}\n      data-id={`node-${node.id}`}\n      width={width}\n      height={height}\n      x={0}\n      y={0}\n    >\n      <span\n        className={styles.textNodeWrapper}\n        data-x={x}\n        data-y={y}\n        data-key={JSON.stringify(text)}\n      >\n        <span className={styles.key} style={{ color: getTextColor({ value, type: typeof value }) }}>\n          <TextRenderer>{value}</TextRenderer>\n        </span>\n      </span>\n    </foreignObject>\n  );\n};\n\nconst propsAreEqual = (prev: TextNodeProps, next: TextNodeProps) => {\n  return prev.node.text === next.node.text && prev.node.width === next.node.width;\n};\n\nexport const TextNode = React.memo(TextNodeBase, propsAreEqual);\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/TextRenderer.module.css",
    "content": ".row {\n  display: inline-flex;\n  align-items: center;\n  overflow: hidden;\n  gap: 4px;\n  vertical-align: middle;\n}\n\n.colorPreview {\n  width: 12px;\n  height: 12px;\n  border-radius: 3px;\n  display: inline-block;\n  border: 1px solid rgba(0, 0, 0, 0.25);\n}\n\n.link {\n  text-decoration: underline;\n  pointer-events: all;\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/TextRenderer.tsx",
    "content": "import React from \"react\";\nimport styles from \"./TextRenderer.module.css\";\n\nconst URL_PATTERN =\n  /^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$/i;\nconst HEX_CODE_PATTERN = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;\nconst RGB_PATTERN = /^rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/;\nconst RGBA_PATTERN = /^rgba\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(0|1|0\\.\\d+)\\s*\\)$/;\n\nconst isURL = (word: string) => {\n  return URL_PATTERN.test(word);\n};\n\nconst isColorFormat = (colorString: string) => {\n  return (\n    HEX_CODE_PATTERN.test(colorString) ||\n    RGB_PATTERN.test(colorString) ||\n    RGBA_PATTERN.test(colorString)\n  );\n};\n\nconst LinkifiedText = ({ text }: { text: string }) => {\n  if (!isURL(text)) return <>{text}</>;\n\n  const href = text.startsWith(\"http://\") || text.startsWith(\"https://\") ? text : `https://${text}`;\n\n  return (\n    <a\n      className={styles.link}\n      onClick={event => event.stopPropagation()}\n      href={href}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n    >\n      {text}\n    </a>\n  );\n};\n\nexport const TextRenderer = ({ children }: React.PropsWithChildren) => {\n  if (typeof children === \"string\" && isColorFormat(children)) {\n    return (\n      <span className={styles.row}>\n        <span className={styles.colorPreview} style={{ backgroundColor: children }} />\n        {children}\n      </span>\n    );\n  }\n\n  if (typeof children === \"string\") {\n    return <LinkifiedText text={children} />;\n  }\n\n  return <>{`${children}`}</>;\n};\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/nodeStyles.ts",
    "content": "type TextColorOptions = {\n  type?: string;\n  value?: string | number | null | boolean;\n};\n\nexport const getTextColor = ({ type, value }: TextColorOptions) => {\n  if (value === null) return \"var(--node-null)\";\n  if (type === \"object\") return \"var(--node-key)\";\n  if (type === \"number\") return \"var(--node-integer)\";\n  if (value === true) return \"var(--node-bool-true)\";\n  if (value === false) return \"var(--node-bool-false)\";\n  return \"var(--node-value)\";\n};\n"
  },
  {
    "path": "packages/jsoncrack-react/src/css-modules.d.ts",
    "content": "declare module \"*.module.css\" {\n  const classes: Record<string, string>;\n  export default classes;\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/src/index.ts",
    "content": "export { JSONCrack } from \"./JSONCrackComponent\";\nexport { parseGraph } from \"./parser\";\nexport type { JSONCrackProps, JSONCrackRef } from \"./JSONCrackComponent\";\nexport type { ParseGraphResult } from \"./parser\";\nexport type {\n  CanvasThemeMode,\n  EdgeData,\n  GraphData,\n  LayoutDirection,\n  NodeData,\n  NodeRow,\n} from \"./types\";\n"
  },
  {
    "path": "packages/jsoncrack-react/src/parser.ts",
    "content": "import { getNodePath, parseTree, type Node, type ParseError } from \"jsonc-parser\";\nimport type { EdgeData, GraphData, NodeData, NodeRow } from \"./types\";\nimport { calculateNodeSize } from \"./utils/calculateNodeSize\";\n\nexport interface ParseGraphResult extends GraphData {\n  errors: ParseError[];\n}\n\nexport const parseGraph = (json: string): ParseGraphResult => {\n  const parseErrors: ParseError[] = [];\n  const jsonTree = parseTree(json, parseErrors);\n\n  if (!jsonTree) {\n    return {\n      nodes: [],\n      edges: [],\n      errors: parseErrors,\n    };\n  }\n\n  const nodes: NodeData[] = [];\n  const edges: EdgeData[] = [];\n  let nodeId = 1;\n  let edgeId = 1;\n\n  function traverse(node: Node, parentId?: string): string | undefined {\n    const id = String(nodeId++);\n    const text: NodeRow[] = [];\n\n    if (parentId !== undefined && node.parent?.type === \"array\") {\n      edges.push({\n        id: String(edgeId++),\n        from: parentId,\n        to: id,\n        text: \"\",\n      });\n    }\n\n    const isArray = node.type === \"array\";\n    const isRootArray = !node.parent || node.parent.type === \"array\";\n\n    if (isArray && isRootArray) {\n      const { width, height } = calculateNodeSize(`[${node.children?.length ?? \"0\"} items]`);\n\n      nodes.push({\n        id,\n        text: [\n          {\n            key: null,\n            value: `[${node.children?.length ?? 0} items]`,\n            type: \"array\",\n            childrenCount: node.children?.length,\n          },\n        ],\n        width,\n        height,\n        path: [],\n      });\n\n      node.children?.forEach(child => {\n        traverse(child, id);\n      });\n\n      return id;\n    }\n\n    node.children?.forEach(child => {\n      if (!child.children || !child.children[1]) {\n        traverse(child, id);\n        return;\n      }\n\n      const key = child.children[0].value?.toString() ?? null;\n      const valueNode = child.children[1];\n      const type = valueNode.type;\n\n      if (type === \"array\") {\n        const targetIds: string[] = [];\n\n        valueNode.children?.forEach(arrayChild => {\n          const arrayChildId = traverse(arrayChild, undefined);\n          if (arrayChildId) targetIds.push(arrayChildId);\n        });\n\n        text.push({\n          key,\n          value: valueNode.value as NodeRow[\"value\"],\n          type,\n          to: targetIds.length > 0 ? targetIds : undefined,\n          childrenCount: valueNode.children?.length,\n        });\n\n        targetIds.forEach(targetId => {\n          edges.push({\n            id: String(edgeId++),\n            from: id,\n            to: targetId,\n            text: key,\n          });\n        });\n      } else if (type === \"object\") {\n        const objectNodeId = traverse(valueNode, id);\n\n        text.push({\n          key,\n          value: valueNode.value as NodeRow[\"value\"],\n          type,\n          childrenCount: Object.keys(valueNode.children ?? {}).length,\n          ...(objectNodeId && { to: [objectNodeId] }),\n        });\n\n        if (objectNodeId) {\n          edges.push({\n            id: String(edgeId++),\n            from: id,\n            to: objectNodeId,\n            text: key,\n          });\n        }\n      } else {\n        text.push({\n          key,\n          value: valueNode.value as NodeRow[\"value\"],\n          type,\n        });\n      }\n    });\n\n    if (node.parent?.type === \"array\" && node.type === \"object\" && node.children?.length === 0) {\n      text.push({\n        key: null,\n        value: \"{0 keys}\",\n        type: \"object\",\n        childrenCount: 0,\n      });\n    }\n\n    const appendParentKey = () => {\n      const getParentKey = (targetNode: Node) => {\n        const path = getNodePath(targetNode);\n        return path?.pop()?.toString();\n      };\n\n      if (!node.parent) {\n        return { parentKey: getParentKey(node), parentType: node.type };\n      }\n\n      if (node.parent.type === \"array\") {\n        return { parentKey: getParentKey(node.parent), parentType: \"array\" };\n      }\n\n      if (node.parent.type === \"property\") {\n        return { parentKey: getParentKey(node), parentType: \"object\" };\n      }\n\n      return {\n        parentKey: getParentKey(node),\n        parentType: node.parent.type.replace(\"property\", \"object\"),\n      };\n    };\n\n    if (text.length === 0) {\n      if (typeof node.value === \"undefined\") return undefined;\n\n      const { width, height } = calculateNodeSize(node.value as string | number);\n\n      nodes.push({\n        id,\n        text: [\n          {\n            key: null,\n            value: node.value as NodeRow[\"value\"],\n            type: node.type,\n          },\n        ],\n        width,\n        height,\n        path: getNodePath(node),\n        ...appendParentKey(),\n      });\n    } else {\n      let displayText: string | [string, string][];\n\n      if (text.some(row => row.key !== null)) {\n        displayText = text.map(row => {\n          const keyStr = row.key === null ? \"\" : row.key;\n\n          if (row.type === \"object\") return [keyStr, `{${row.childrenCount ?? 0} keys}`];\n          if (row.type === \"array\") return [keyStr, `[${row.childrenCount ?? 0} items]`];\n          if (row.value === null) return [keyStr, \"null\"];\n\n          return [keyStr, `${row.value}`];\n        });\n      } else {\n        displayText = `${text[0].value}`;\n      }\n\n      const { width, height } = calculateNodeSize(displayText);\n\n      nodes.push({\n        id,\n        text,\n        width,\n        height,\n        path: getNodePath(node),\n        ...appendParentKey(),\n      });\n    }\n\n    return id;\n  }\n\n  traverse(jsonTree);\n\n  return {\n    nodes,\n    edges,\n    errors: parseErrors,\n  };\n};\n"
  },
  {
    "path": "packages/jsoncrack-react/src/theme.ts",
    "content": "import type { CanvasThemeMode } from \"./types\";\n\nexport interface JSONCrackTheme {\n  NODE_COLORS: {\n    TEXT: string;\n    NODE_KEY: string;\n    NODE_VALUE: string;\n    INTEGER: string;\n    NULL: string;\n    BOOL: {\n      FALSE: string;\n      TRUE: string;\n    };\n    CHILD_COUNT: string;\n    DIVIDER: string;\n  };\n  INTERACTIVE_NORMAL: string;\n  BACKGROUND_NODE: string;\n  BACKGROUND_MODIFIER_ACCENT: string;\n  TEXT_POSITIVE: string;\n  GRID_BG_COLOR: string;\n  GRID_COLOR_PRIMARY: string;\n  GRID_COLOR_SECONDARY: string;\n}\n\nexport const themes: Record<CanvasThemeMode, JSONCrackTheme> = {\n  dark: {\n    NODE_COLORS: {\n      TEXT: \"#DCE5E7\",\n      NODE_KEY: \"#59b8ff\",\n      NODE_VALUE: \"#DCE5E7\",\n      INTEGER: \"#e8c479\",\n      NULL: \"#939598\",\n      BOOL: {\n        FALSE: \"#F85C50\",\n        TRUE: \"#00DC7D\",\n      },\n      CHILD_COUNT: \"#FFFFFF\",\n      DIVIDER: \"#383838\",\n    },\n    INTERACTIVE_NORMAL: \"#b9bbbe\",\n    BACKGROUND_NODE: \"#2B2C3E\",\n    BACKGROUND_MODIFIER_ACCENT: \"rgba(79,84,92,0.48)\",\n    TEXT_POSITIVE: \"hsl(139,calc(var(--saturation-factor, 1)*51.6%),52.2%)\",\n    GRID_BG_COLOR: \"#141414\",\n    GRID_COLOR_PRIMARY: \"#1c1b1b\",\n    GRID_COLOR_SECONDARY: \"#191919\",\n  },\n  light: {\n    NODE_COLORS: {\n      TEXT: \"#000000\",\n      NODE_KEY: \"#761CEA\",\n      NODE_VALUE: \"#535353\",\n      INTEGER: \"#FD0079\",\n      NULL: \"#afafaf\",\n      BOOL: {\n        FALSE: \"#FF0000\",\n        TRUE: \"#748700\",\n      },\n      CHILD_COUNT: \"#535353\",\n      DIVIDER: \"#e6e6e6\",\n    },\n    INTERACTIVE_NORMAL: \"#4f5660\",\n    BACKGROUND_NODE: \"#F6F8FA\",\n    BACKGROUND_MODIFIER_ACCENT: \"rgba(106,116,128,0.24)\",\n    TEXT_POSITIVE: \"#008736\",\n    GRID_BG_COLOR: \"#f7f7f7\",\n    GRID_COLOR_PRIMARY: \"#ebe8e8\",\n    GRID_COLOR_SECONDARY: \"#f2eeee\",\n  },\n};\n"
  },
  {
    "path": "packages/jsoncrack-react/src/types.ts",
    "content": "import type { JSONPath, Node } from \"jsonc-parser\";\n\nexport interface NodeRow {\n  key: string | null;\n  value: string | number | null | boolean;\n  type: Node[\"type\"];\n  childrenCount?: number;\n  to?: string[];\n}\n\nexport interface NodeData {\n  id: string;\n  text: Array<NodeRow>;\n  width: number;\n  height: number;\n  path?: JSONPath;\n  parentKey?: string;\n  parentType?: string;\n}\n\nexport interface EdgeData {\n  id: string;\n  from: string;\n  to: string;\n  text: string | null;\n}\n\nexport interface GraphData {\n  nodes: NodeData[];\n  edges: EdgeData[];\n}\n\nexport type LayoutDirection = \"LEFT\" | \"RIGHT\" | \"DOWN\" | \"UP\";\n\nexport type CanvasThemeMode = \"light\" | \"dark\";\n"
  },
  {
    "path": "packages/jsoncrack-react/src/utils/calculateNodeSize.ts",
    "content": "const NODE_DIMENSIONS = {\n  ROW_HEIGHT: 30,\n  PARENT_HEIGHT: 36,\n} as const;\n\ntype Text = number | string | [string, string][];\ntype Size = { width: number; height: number };\n\nconst CACHE_TTL_MS = 120_000;\nconst sizeCache = new Map<string, Size>();\nlet lastCacheClearAt = Date.now();\n\nconst calculateLines = (text: Text): string => {\n  if (Array.isArray(text)) {\n    return text.map(([k, v]) => `${k}: ${JSON.stringify(v).slice(0, 80)}`).join(\"\\n\");\n  }\n\n  return `${text}`;\n};\n\nconst fallbackSize = (str: string, single: boolean): Size => {\n  const lines = str.split(\"\\n\");\n  const longestLine = lines.reduce((max, line) => Math.max(max, line.length), 0);\n\n  return {\n    width: Math.min(700, Math.max(45, longestLine * 8 + 24)),\n    height: single ? NODE_DIMENSIONS.PARENT_HEIGHT : lines.length * NODE_DIMENSIONS.ROW_HEIGHT,\n  };\n};\n\nconst calculateWidthAndHeight = (str: string, single = false): Size => {\n  if (!str) return { width: 45, height: 45 };\n\n  if (typeof document === \"undefined\") {\n    return fallbackSize(str, single);\n  }\n\n  const dummyElement = document.createElement(\"div\");\n  dummyElement.style.position = \"absolute\";\n  dummyElement.style.visibility = \"hidden\";\n  dummyElement.style.pointerEvents = \"none\";\n  dummyElement.style.whiteSpace = single ? \"nowrap\" : \"pre-wrap\";\n  dummyElement.innerText = str;\n  dummyElement.style.fontSize = \"12px\";\n  dummyElement.style.width = \"fit-content\";\n  dummyElement.style.padding = \"0 10px\";\n  dummyElement.style.fontWeight = \"500\";\n  dummyElement.style.fontFamily = \"monospace\";\n  document.body.appendChild(dummyElement);\n\n  const clientRect = dummyElement.getBoundingClientRect();\n  const lines = str.split(\"\\n\").length;\n\n  const width = clientRect.width + 4;\n  const height = single ? NODE_DIMENSIONS.PARENT_HEIGHT : lines * NODE_DIMENSIONS.ROW_HEIGHT;\n\n  document.body.removeChild(dummyElement);\n  return { width, height };\n};\n\nconst maybeClearCache = () => {\n  if (Date.now() - lastCacheClearAt < CACHE_TTL_MS) return;\n  sizeCache.clear();\n  lastCacheClearAt = Date.now();\n};\n\nexport const calculateNodeSize = (text: Text, isParent = false) => {\n  maybeClearCache();\n\n  const cacheKey = `${JSON.stringify(text)}-${isParent}`;\n\n  const cached = sizeCache.get(cacheKey);\n  if (cached) return cached;\n\n  const lines = calculateLines(text);\n  const sizes = calculateWidthAndHeight(lines, typeof text === \"string\");\n\n  if (isParent) sizes.width += 80;\n  if (sizes.width > 700) sizes.width = 700;\n\n  sizeCache.set(cacheKey, sizes);\n  return sizes;\n};\n"
  },
  {
    "path": "packages/jsoncrack-react/tsconfig.build.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"lib\": [\"DOM\", \"ES2020\"],\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"emitDeclarationOnly\": true,\n    \"outDir\": \"dist\",\n    \"rootDir\": \"src\",\n    \"jsx\": \"react-jsx\",\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig.build.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/vite.config.ts",
    "content": "import react from \"@vitejs/plugin-react\";\nimport { resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineConfig } from \"vite\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\n\nexport default defineConfig({\n  plugins: [react()],\n  build: {\n    lib: {\n      entry: resolve(__dirname, \"src/index.ts\"),\n      formats: [\"es\"],\n      fileName: \"index\",\n    },\n    rolldownOptions: {\n      external: [\n        \"react\",\n        \"react-dom\",\n        \"react/jsx-runtime\",\n        \"reaflow\",\n        \"react-zoomable-ui\",\n        \"jsonc-parser\",\n      ],\n      output: {\n        assetFileNames: \"index[extname]\",\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - apps/*\n  - packages/*\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turborepo.com/schema.json\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\".next/**\", \"!.next/cache/**\", \"dist/**\", \"out/**\", \"build/**\"]\n    },\n    \"dev\": {\n      \"dependsOn\": [\"^build\"],\n      \"cache\": false,\n      \"persistent\": true\n    },\n    \"start\": {\n      \"cache\": false,\n      \"persistent\": true\n    },\n    \"lint\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": []\n    },\n    \"lint:fix\": {\n      \"cache\": false\n    },\n    \"analyze\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\".next/**\", \"!.next/cache/**\", \"out/**\", \"build/**\"]\n    },\n    \"clean\": {\n      \"cache\": false\n    },\n    \"watch-build\": {\n      \"cache\": false,\n      \"persistent\": true\n    }\n  }\n}\n"
  }
]