Full Code of AykutSarac/jsoncrack.com for AI

main 73a51d3bb295 cached
203 files
349.7 KB
95.5k tokens
102 symbols
1 requests
Download .txt
Showing preview only (396K chars total). Download the full file or copy to clipboard to get everything.
Repository: AykutSarac/jsoncrack.com
Branch: main
Commit: 73a51d3bb295
Files: 203
Total size: 349.7 KB

Directory structure:
gitextract_0apmokt3/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── deploy.yml
│       └── pull-request.yml
├── .gitignore
├── .npmrc
├── .vscode/
│   ├── launch.json
│   └── tasks.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── apps/
│   ├── vscode/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc
│   │   ├── .vscodeignore
│   │   ├── LICENSE.md
│   │   ├── README.md
│   │   ├── esbuild.config.mjs
│   │   ├── eslint.config.mjs
│   │   ├── ext-src/
│   │   │   ├── extension.ts
│   │   │   └── webview.ts
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── App.tsx
│   │   │   ├── components/
│   │   │   │   └── NodeModal.tsx
│   │   │   ├── env.d.ts
│   │   │   ├── global.css
│   │   │   └── index.tsx
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   └── www/
│       ├── .dockerignore
│       ├── .gitignore
│       ├── .prettierignore
│       ├── .prettierrc
│       ├── Dockerfile
│       ├── LICENSE.md
│       ├── docker-compose.yml
│       ├── eslint.config.mjs
│       ├── next-env.d.ts
│       ├── next-sitemap.config.js
│       ├── next.config.js
│       ├── nginx.conf
│       ├── package.json
│       ├── public/
│       │   ├── .nojekyll
│       │   ├── CNAME
│       │   ├── manifest.json
│       │   ├── robots.txt
│       │   ├── sitemap-0.xml
│       │   └── sitemap.xml
│       ├── shims/
│       │   └── empty.ts
│       ├── src/
│       │   ├── constants/
│       │   │   ├── globalStyle.ts
│       │   │   ├── graph.ts
│       │   │   ├── seo.ts
│       │   │   └── theme.ts
│       │   ├── data/
│       │   │   ├── example.json
│       │   │   ├── faq.json
│       │   │   ├── privacy.json
│       │   │   └── terms.json
│       │   ├── enums/
│       │   │   ├── file.enum.ts
│       │   │   └── viewMode.enum.ts
│       │   ├── features/
│       │   │   ├── Banner.tsx
│       │   │   ├── editor/
│       │   │   │   ├── BottomBar.tsx
│       │   │   │   ├── ExternalMode.tsx
│       │   │   │   ├── FullscreenDropzone.tsx
│       │   │   │   ├── LiveEditor.tsx
│       │   │   │   ├── TextEditor.tsx
│       │   │   │   ├── Toolbar/
│       │   │   │   │   ├── FileMenu.tsx
│       │   │   │   │   ├── SearchInput.tsx
│       │   │   │   │   ├── ThemeToggle.tsx
│       │   │   │   │   ├── ToolsMenu.tsx
│       │   │   │   │   ├── ViewMenu.tsx
│       │   │   │   │   ├── index.tsx
│       │   │   │   │   └── styles.ts
│       │   │   │   └── views/
│       │   │   │       ├── GraphView/
│       │   │   │       │   ├── CustomEdge/
│       │   │   │       │   │   └── index.tsx
│       │   │   │       │   ├── CustomNode/
│       │   │   │       │   │   ├── ObjectNode.tsx
│       │   │   │       │   │   ├── TextNode.tsx
│       │   │   │       │   │   ├── TextRenderer.tsx
│       │   │   │       │   │   ├── index.tsx
│       │   │   │       │   │   └── styles.tsx
│       │   │   │       │   ├── NotSupported.tsx
│       │   │   │       │   ├── OptionsMenu.tsx
│       │   │   │       │   ├── SecureInfo.tsx
│       │   │   │       │   ├── ZoomControl.tsx
│       │   │   │       │   ├── index.tsx
│       │   │   │       │   ├── lib/
│       │   │   │       │   │   ├── jsonParser.ts
│       │   │   │       │   │   └── utils/
│       │   │   │       │   │       ├── calculateNodeSize.ts
│       │   │   │       │   │       ├── getChildrenEdges.ts
│       │   │   │       │   │       └── getOutgoers.ts
│       │   │   │       │   └── stores/
│       │   │   │       │       └── useGraph.ts
│       │   │   │       └── TreeView/
│       │   │   │           ├── Label.tsx
│       │   │   │           ├── Value.tsx
│       │   │   │           └── index.tsx
│       │   │   └── modals/
│       │   │       ├── DownloadModal/
│       │   │       │   └── index.tsx
│       │   │       ├── ImportModal/
│       │   │       │   └── index.tsx
│       │   │       ├── JPathModal/
│       │   │       │   └── index.tsx
│       │   │       ├── JQModal/
│       │   │       │   └── index.tsx
│       │   │       ├── ModalController.tsx
│       │   │       ├── NodeModal/
│       │   │       │   └── index.tsx
│       │   │       ├── SchemaModal/
│       │   │       │   └── index.tsx
│       │   │       ├── TypeModal/
│       │   │       │   └── index.tsx
│       │   │       ├── index.ts
│       │   │       └── modalTypes.ts
│       │   ├── hooks/
│       │   │   ├── useFocusNode.ts
│       │   │   └── useJsonQuery.ts
│       │   ├── layout/
│       │   │   ├── ConverterLayout/
│       │   │   │   ├── PageLinks.tsx
│       │   │   │   ├── ToolPage.tsx
│       │   │   │   └── options.ts
│       │   │   ├── JSONCrackBrandLogo.tsx
│       │   │   ├── Landing/
│       │   │   │   ├── FAQ.tsx
│       │   │   │   ├── Features.tsx
│       │   │   │   ├── HeroPreview.tsx
│       │   │   │   ├── HeroSection.tsx
│       │   │   │   ├── Section1.tsx
│       │   │   │   ├── Section2.tsx
│       │   │   │   └── Section3.tsx
│       │   │   ├── PageLayout/
│       │   │   │   ├── Footer.tsx
│       │   │   │   ├── Navbar.tsx
│       │   │   │   └── index.tsx
│       │   │   └── TypeLayout/
│       │   │       ├── PageLinks.tsx
│       │   │       └── TypegenWrapper.tsx
│       │   ├── lib/
│       │   │   └── utils/
│       │   │       ├── generateType.ts
│       │   │       ├── helpers.ts
│       │   │       ├── json2go.js
│       │   │       ├── jsonAdapter.ts
│       │   │       ├── mantineColorScheme.ts
│       │   │       └── search.ts
│       │   ├── pages/
│       │   │   ├── 404.tsx
│       │   │   ├── _app.tsx
│       │   │   ├── _document.tsx
│       │   │   ├── _error.tsx
│       │   │   ├── converter/
│       │   │   │   ├── csv-to-json.tsx
│       │   │   │   ├── csv-to-xml.tsx
│       │   │   │   ├── csv-to-yaml.tsx
│       │   │   │   ├── json-to-csv.tsx
│       │   │   │   ├── json-to-xml.tsx
│       │   │   │   ├── json-to-yaml.tsx
│       │   │   │   ├── xml-to-csv.tsx
│       │   │   │   ├── xml-to-json.tsx
│       │   │   │   ├── xml-to-yaml.tsx
│       │   │   │   ├── yaml-to-csv.tsx
│       │   │   │   ├── yaml-to-json.tsx
│       │   │   │   └── yaml-to-xml.tsx
│       │   │   ├── docs.tsx
│       │   │   ├── editor.tsx
│       │   │   ├── index.tsx
│       │   │   ├── legal/
│       │   │   │   ├── privacy.tsx
│       │   │   │   └── terms.tsx
│       │   │   ├── tools/
│       │   │   │   └── json-schema.tsx
│       │   │   ├── type/
│       │   │   │   ├── csv-to-go.tsx
│       │   │   │   ├── csv-to-kotlin.tsx
│       │   │   │   ├── csv-to-rust.tsx
│       │   │   │   ├── csv-to-typescript.tsx
│       │   │   │   ├── json-to-go.tsx
│       │   │   │   ├── json-to-kotlin.tsx
│       │   │   │   ├── json-to-rust.tsx
│       │   │   │   ├── json-to-typescript.tsx
│       │   │   │   ├── xml-to-go.tsx
│       │   │   │   ├── xml-to-kotlin.tsx
│       │   │   │   ├── xml-to-rust.tsx
│       │   │   │   ├── xml-to-typescript.tsx
│       │   │   │   ├── yaml-to-go.tsx
│       │   │   │   ├── yaml-to-kotlin.tsx
│       │   │   │   ├── yaml-to-rust.tsx
│       │   │   │   └── yaml-to-typescript.tsx
│       │   │   └── widget.tsx
│       │   ├── store/
│       │   │   ├── useConfig.ts
│       │   │   ├── useFile.ts
│       │   │   ├── useJson.ts
│       │   │   └── useModal.ts
│       │   └── types/
│       │       └── styled.d.ts
│       └── tsconfig.json
├── package.json
├── packages/
│   └── jsoncrack-react/
│       ├── .gitignore
│       ├── .prettierrc
│       ├── LICENSE.md
│       ├── README.md
│       ├── eslint.config.mjs
│       ├── package.json
│       ├── src/
│       │   ├── JSONCrackComponent.tsx
│       │   ├── JSONCrackStyles.module.css
│       │   ├── components/
│       │   │   ├── Controls.module.css
│       │   │   ├── Controls.tsx
│       │   │   ├── CustomEdge.tsx
│       │   │   ├── CustomNode.tsx
│       │   │   ├── Node.module.css
│       │   │   ├── ObjectNode.tsx
│       │   │   ├── TextNode.tsx
│       │   │   ├── TextRenderer.module.css
│       │   │   ├── TextRenderer.tsx
│       │   │   └── nodeStyles.ts
│       │   ├── css-modules.d.ts
│       │   ├── index.ts
│       │   ├── parser.ts
│       │   ├── theme.ts
│       │   ├── types.ts
│       │   └── utils/
│       │       └── calculateNodeSize.ts
│       ├── tsconfig.build.json
│       ├── tsconfig.json
│       └── vite.config.ts
├── pnpm-workspace.yaml
└── turbo.json

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

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Create a report to help us improve
title: "[BUG]: <Context of the issue>"
labels: bug
body:
  - type: textarea
    id: description
    attributes:
      label: Issue description
      description: |
        Describe the issue in as much detail as possible.

        Tip: You can attach images or log files by clicking this area to highlight it and then dragging files into it.
      placeholder: |
        Steps to reproduce with below code sample:
        1. do thing
        2. click...
        3. observe behavior
        4. see error logs below
    validations:
      required: true
  - type: textarea
    id: media
    attributes:
      label: Media & Screenshots
      description: Include screenshots or video of reproduction as much as possible
  - type: textarea
    id: os
    attributes:
      label: Operating system
      description: Which OS does your application run on?
      value: |
       - OS: [e.g. iOS]:
       - Browser [e.g. chrome, safari]:

       - Any other details...
  - type: dropdown
    id: priority
    attributes:
      label: Priority this issue should have
      description: Please be realistic. If you need to elaborate on your reasoning, please use the Issue description field above.
      options:
        - Low (slightly annoying)
        - Medium (should be fixed soon)
        - High (immediate attention needed)
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Request a new feature
labels: [feature]
body:
  - type: textarea
    id: description
    attributes:
      label: Feature
      description: A clear and concise description of what the problem is, or what feature you want to be implemented.
      placeholder: I'm always frustrated when..., Discord has recently released..., A good addition would be...
    validations:
      required: true
  - type: textarea
    id: alternatives
    attributes:
      label: Alternative solutions or implementations
      description: A clear and concise description of any alternative solutions or features you have considered.
  - type: textarea
    id: additional-context
    attributes:
      label: Other context
      description: Any other context, screenshots, or file uploads that help us understand your feature request.


================================================
FILE: .github/pull_request_template.md
================================================
## Issue
Closes #[ISSUE_NUMBER]

## What Changed
Brief description of what you changed and why.

Example:
- Added JSON validation tooltip on parse error
- Improved performance by memoizing graph component
- Fixed bug where large JSON files caused slowdown

## How to Test
Step-by-step instructions to test the change:

1. ...
2. ...
3. ...

## Evidence
**Screenshots or Video Required** — Choose one or both:

### Screenshots
- [ ] Before/after screenshots attached (for UI changes)

### Video
- [ ] Screen recording of the feature in action

### Testing
- [ ] Tested locally with `pnpm dev`
- [ ] No console errors
- [ ] Tested with large JSON files (if applicable)

## Performance Impact
Any notes on performance, re-renders, or potential concerns?

- [ ] No performance impact
- [ ] Improved performance (explain how)
- [ ] Potential impact (explain and justify)

## Additional Notes
Anything else reviewers should know?

---

**Remember:** If this PR is not linked to an issue, it may be closed. Always reference an approved issue!


================================================
FILE: .github/workflows/deploy.yml
================================================
name: Deploy Next.js site to Pages

on:
  push:
    branches: ["main"]

  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: "24.10.0"

      - uses: pnpm/action-setup@v4
        name: Install pnpm
        with:
          version: 10.20.0
          run_install: false

      - name: Get pnpm store directory
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - uses: actions/cache@v5
        name: Setup pnpm cache
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Restore Turbo and Next.js cache
        uses: actions/cache@v5
        with:
          path: |
            .turbo
            apps/www/.next/cache
          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') }}
          restore-keys: |
            ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build with Turborepo
        run: pnpm run build:www
        env:
          NEXT_PUBLIC_GA_MEASUREMENT_ID: ${{ vars.NEXT_PUBLIC_GA_MEASUREMENT_ID }}

      - name: Upload Build Artifacts
        uses: actions/upload-artifact@v6
        with:
          name: build-files
          path: ./apps/www/out/_next/static/chunks

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v4
        with:
          path: './apps/www/out'

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


================================================
FILE: .github/workflows/pull-request.yml
================================================
name: Verify Pull Request

on:
  pull_request:
    branches: [ "main" ]

jobs:
  cache-and-install:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Install Node.js
        uses: actions/setup-node@v6
        with:
          node-version: "24.10.0"

      - uses: pnpm/action-setup@v4
        name: Install pnpm
        with:
          version: 10.20.0
          run_install: false

      - name: Get pnpm store directory
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - uses: actions/cache@v5
        name: Setup pnpm cache
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Restore Turbo and Next.js cache
        uses: actions/cache@v5
        with:
          path: |
            .turbo
            apps/www/.next/cache
          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') }}
          restore-keys: |
            ${{ runner.os }}-turbo-next-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Lint
        run: pnpm turbo run lint

      - name: Build
        run: pnpm turbo run build

        


================================================
FILE: .gitignore
================================================
# Agent tooling
.agents
.claude
.codex
skills-lock.json

# Package manager
node_modules/
.npm-cache/
.pnpm-store/
.pnp*
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*

# Turborepo
.turbo/

# Build outputs and caches (all workspaces)
**/.next/
**/out/
**/coverage/
**/*.tsbuildinfo

# OS
.DS_Store


================================================
FILE: .npmrc
================================================
engine-strict=true


================================================
FILE: .vscode/launch.json
================================================
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Run VSCode Extension",
      "type": "extensionHost",
      "request": "launch",
      "args": ["--extensionDevelopmentPath=${workspaceFolder}/apps/vscode"],
      "outFiles": ["${workspaceFolder}/apps/vscode/build/**/*.js"],
      "preLaunchTask": "build vscode extension"
    }
  ]
}


================================================
FILE: .vscode/tasks.json
================================================
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build vscode extension",
      "type": "shell",
      "command": "pnpm run build:vscode",
      "group": {
        "kind": "build",
        "isDefault": true
      }
    },
    {
      "label": "watch vscode extension",
      "type": "shell",
      "command": "pnpm run dev:vscode",
      "isBackground": true
    }
  ]
}


================================================
FILE: CODE_OF_CONDUCT.md
================================================

# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
  community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or advances of
  any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
  without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
opensource@github.com.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of
actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the
community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to JSON Crack

Thank 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.

**Read our [Code of Conduct](./CODE_OF_CONDUCT.md) first** — we want to keep this community friendly and welcoming.

---

## Before You Start: The Issue-First Workflow

**Always open or find an issue BEFORE you start coding.** This saves everyone time.

1. **Check existing issues** — Search to see if someone already reported this or is working on it
2. **Open a new issue** if one doesn't exist — Describe what you want to fix or build
3. **Wait for approval** — I'll review and give feedback (usually within a few days)
4. **Once approved**, you can start coding
5. **Link your PR to the issue** — Use `Closes #123` in your PR description

This workflow prevents duplicate work and ensures your contribution aligns with the project's direction.

---

## Quick Setup

### Prerequisites
- Node.js 18+
- pnpm (or npm/yarn)

### Tech Stack
JSON Crack uses:
- **React** — UI library
- **Reaflow** — Graph visualization
- **Mantine UI** — UI components
- **Zustand** — State management

### Get Started
```bash
# Clone the repo
git clone https://github.com/AykutSarac/jsoncrack.com.git
cd jsoncrack.com

# Install dependencies
pnpm install

# Run the dev server
pnpm dev
```

The app will be available at `http://localhost:3000`

---

## How to Submit a Pull Request

### Requirements
Before submitting, make sure your PR includes:

1. **Issue ID** — Reference the issue: `Closes #123`
2. **Clear description** — What does this change do? Why?
3. **Evidence of working changes** — One or both:
   - **Screenshot** — Show the UI before/after
   - **Video** — Screen recording of the feature in action
4. **Test it locally** — Run `pnpm dev` and verify it works
5. **Follow code style** — Use [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html)

### Creating Your Branch
```bash
git checkout -b fix/issue-123-description
# or
git checkout -b feature/issue-123-description
```

Use clear branch names that reference the issue.

---

## Guidelines

### Performance First
- Avoid unnecessary re-renders
- Use React DevTools Profiler to check performance
- Test with large JSON files to ensure no slowdowns

### Code Quality
- Follow the [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html)
- Write descriptive commit messages
- Keep changes focused — one feature/fix per PR

### Testing
- Manually test your changes thoroughly
- Describe exactly how you tested it in the PR
- Make sure existing features still work

---

## Example PR

Here's what a good PR looks like:

**Title:** Add JSON validation tooltip on parse error

**Description:**
```
Closes #234

## What Changed
Added a helpful tooltip that shows validation errors when JSON fails to parse, making it easier for users to fix their JSON.

## How to Test
1. Paste invalid JSON: `{invalid`
2. Look for the red error indicator
3. Hover over it to see the detailed error message

## Evidence
- [Screenshot of tooltip](link-to-image)

## Performance Notes
No performance impact. Tooltip renders conditionally only on errors.
```

---

## Questions?

- Found a bug? Open an issue
- Have an idea? Open an issue
- Confused about something? Comment on the issue

Thank you for contributing to JSON Crack! 🎉


================================================
FILE: LICENSE.md
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2025 Aykut Saraç

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
<!-- PROJECT LOGO -->
<p align="center">
  <a href="https://github.com/AykutSarac/jsoncrack.com">
   <img src="./apps/www/public/assets/192.png" height="50" alt="Logo">
  </a>

  <h1 align="center">JSON Crack</h1>

  <p align="center">
    The open-source JSON Editor.
    <br />
    <a href="https://jsoncrack.com"><strong>Learn more »</strong></a>
    <br />
    <br />
    <a href="https://todiagram.com">ToDiagram</a>
    ·
    <a href="https://discord.gg/yVyTtCRueq">Discord</a>
    ·
    <a href="https://jsoncrack.com">Website</a>
    ·
    <a href="https://github.com/AykutSarac/jsoncrack.com/issues">Issues</a>
    ·
    <a href="https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode">VS Code</a>
  </p>
</p>

<!-- ABOUT THE PROJECT -->

## About the Project

<img width="100%" alt="booking-screen" src="./apps/www/public/assets/editor.webp">

## Visualize JSON into interactive graphs

JSON 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.

* **Visualizer**: Instantly convert JSON, YAML, CSV, and XML into interactive graphs or trees in dark or light mode.
* **Convert**: Seamlessly transform data formats, like JSON to CSV or XML to JSON, for easy sharing.
* **Format & Validate**: Beautify and validate JSON, YAML, and CSV for clear and accurate data.
* **Code Generation**: Generate TypeScript interfaces, Golang structs, and JSON Schema.
* **JSON Schema**: Create JSON Schema, mock data, and validate various data formats.
* **Advanced Tools**: Decode JWT, randomize data, and run jq or JSON path queries.
* **Export Image**: Download your visualization as PNG, JPEG, or SVG.
* **Privacy**: All data processing is local; nothing is stored on our servers.

## Recognition

<a href="https://news.ycombinator.com/item?id=32626873">
  <img
    style="width: 250px; height: 54px;" width="250" height="54"
    alt="Featured on Hacker News"
    src="https://hackernews-badge.vercel.app/api?id=32626873"
  />
</a>

<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>

## Integrations

- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode)
- [npm Package (`jsoncrack-react`)](https://www.npmjs.com/package/jsoncrack-react)

## Contributing

- Found a bug or missing feature? Open an issue on [GitHub Issues](https://github.com/AykutSarac/jsoncrack.com/issues).
- Want to contribute code or docs? Start with our [contribution guide](./CONTRIBUTING.md).

## Sponsors & Support

If you find JSON Crack useful, you can support the project by using [ToDiagram](https://todiagram.com).

## Stay Up-to-Date

JSON 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:

<a href="https://github.com/AykutSarac/jsoncrack.com"><img src="https://img.shields.io/github/stars/AykutSarac/jsoncrack.com" alt="Star at GitHub" /></a>

<!-- GETTING STARTED -->

## Getting Started

To get a local copy up and running, please follow these simple steps.

### Prerequisites

Here is what you need to be able to run JSON Crack.

- Node.js (Version: >=24.x)
- pnpm (Version: >=10)


## Development

### Setup

1. 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.

   ```sh
   git clone https://github.com/AykutSarac/jsoncrack.com.git
   ```

2. Go to the project folder

   ```sh
   cd jsoncrack.com
   ```

3. Install packages

   ```sh
   pnpm install
   ```

4. Run the web app

   ```sh
   pnpm dev:www

   # Running on http://localhost:3000/
   ```

### Useful Commands

From repository root:

```sh
# Web app
pnpm dev:www
pnpm build:www

# VS Code extension
pnpm dev:vscode
pnpm build:vscode
pnpm lint:vscode
pnpm lint:fix:vscode

# All workspaces
pnpm dev
pnpm build
pnpm lint
```

`pnpm build:www` is the production build command used in GitHub Actions deployment.

### Debug VS Code Extension

1. Open repository root in VS Code.
2. Press `F5`.
3. Select `Run VSCode Extension (apps/vscode)` when prompted.
4. In the Extension Development Host window, open a `.json` file and run:
   `JSON Crack: Enable JSON Crack visualization`.

### Docker

🐳 Docker assets are in `apps/www`.
If you want to run JSON Crack locally:

```console
cd apps/www

# Build a Docker image with:
docker compose build

# Run locally with `docker-compose`
docker compose up

# Go to http://localhost:8888
```

## Configuration

The supported node limit can be changed by editing `NEXT_PUBLIC_NODE_LIMIT` in `apps/www/.env`.

<!-- LICENSE -->

## License

See [`LICENSE`](/LICENSE.md) for more information.


================================================
FILE: apps/vscode/.gitignore
================================================
node_modules/
build/
*.vsix

================================================
FILE: apps/vscode/.prettierignore
================================================
.github
.next
node_modules/
out
public
*-lock.json
tsconfig.json
build
jsoncrack

================================================
FILE: apps/vscode/.prettierrc
================================================
{
  "trailingComma": "es5",
  "singleQuote": false,
  "semi": true,
  "printWidth": 100,
  "arrowParens": "avoid",
  "importOrder": [
    "^(react/(.*)$)|^(react$)",
    "^(next/(.*)$)|^(next$)",
    "^@mantine/core",
    "^@mantine",
    "styled",
    "<THIRD_PARTY_MODULES>",
    "^src/(.*)$",
    "^[./]"
  ],
  "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
  "plugins": ["@trivago/prettier-plugin-sort-imports"]
}


================================================
FILE: apps/vscode/.vscodeignore
================================================
.vscode/**
.vscode-test/**
.github/**
.turbo/**
node_modules/**
src/**
ext-src/**
.gitignore
.prettierignore
.prettierrc
index.html
vite.config.ts
esbuild.config.mjs
eslint.config.mjs
**/tsconfig.json
**/*.map


================================================
FILE: apps/vscode/LICENSE.md
================================================
MIT License

Copyright (c) 2025 Aykut Saraç

Permission 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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE 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.

================================================
FILE: apps/vscode/README.md
================================================
  <img src="https://github.com/AykutSarac/jsoncrack-vscode/assets/47941171/23b26537-7c4a-4029-af78-456dea0d0b04" width="300" alt="JSON Crack" />

<hr />

[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.

## How to use?

1. Install the JSON Crack extension from the [VS Code marketplace](https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode).
2. Open a JSON file.
3. Click on the JSON Crack icon in the menubar at top right.

<img width="600" alt="image" src="https://github.com/AykutSarac/jsoncrack-vscode/assets/47941171/06715ac1-2403-402f-b3fa-3d91e1c9196a">

## Privacy

The extension works **fully offline**. No data is sent to any server. All JSON parsing and visualization happens locally in your editor.

## Development

This extension lives in `apps/vscode` inside the [jsoncrack.com](https://github.com/AykutSarac/jsoncrack.com) monorepo.

**Prerequisites:** Node.js `>=20`, pnpm `>=10`

**Stack:** Vite (webview) + esbuild (extension host) + React 19

```sh
# Install dependencies from repo root
pnpm install

# Build the extension
cd apps/vscode
pnpm run build
```

### Debugging

1. Open the **monorepo root** in VS Code.
2. Press **F5** to launch the "Run VSCode Extension" config — it builds and opens the Extension Development Host.
3. After making changes, press `Cmd+R` (macOS) / `Ctrl+R` (Windows/Linux) in the host window to reload.

### Scripts

| Script | Description |
|---|---|
| `build` | Production build (minified, no sourcemaps) |
| `build:dev` | Dev build (sourcemaps, no minification) |
| `watch` | Watch extension host (`ext-src/`) for changes |
| `watch:webview` | Watch webview (`src/`) for changes |
| `dev` | Start Vite dev server (standalone webview in browser) |
| `lint` | Run ESLint + Prettier check |
| `clean` | Remove `build/` directory |


================================================
FILE: apps/vscode/esbuild.config.mjs
================================================
import * as esbuild from "esbuild";

const isWatch = process.argv.includes("--watch");
const isProduction = process.argv.includes("--production");

/** @type {esbuild.BuildOptions} */
const config = {
  entryPoints: ["ext-src/extension.ts"],
  bundle: true,
  outdir: "build",
  external: ["vscode"],
  format: "cjs",
  platform: "node",
  target: "node20",
  sourcemap: !isProduction,
  minify: isProduction,
  logLevel: "info",
};

if (isWatch) {
  const ctx = await esbuild.context(config);
  await ctx.watch();
  console.log("Watching for changes...");
} else {
  await esbuild.build(config);
}


================================================
FILE: apps/vscode/eslint.config.mjs
================================================
import eslint from "@eslint/js";
import { defineConfig, globalIgnores } from "eslint/config";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import eslintPluginPrettier from "eslint-plugin-prettier/recommended";
import unusedImports from "eslint-plugin-unused-imports";
import globals from "globals";
import tseslint from "typescript-eslint";

export default defineConfig([
  eslint.configs.recommended,
  tseslint.configs.recommended,
  eslintConfigPrettier,
  eslintPluginPrettier,
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
      parserOptions: {
        ecmaVersion: "latest",
        sourceType: "module",
        tsconfigRootDir: import.meta.dirname,
      },
    },
    plugins: {
      "unused-imports": unusedImports,
    },
    rules: {
      "@typescript-eslint/consistent-type-imports": "error",
      "unused-imports/no-unused-imports": "error",
      "@typescript-eslint/no-explicit-any": "off",
      "prettier/prettier": "error",
      "space-in-parens": "error",
      "no-empty": "error",
      "no-multiple-empty-lines": "error",
      "no-irregular-whitespace": "error",
      strict: ["error", "never"],
      "linebreak-style": ["error", "unix"],
      quotes: ["error", "double", { avoidEscape: true }],
      semi: ["error", "always"],
      "prefer-const": "error",
      "space-before-function-paren": [
        "error",
        {
          anonymous: "always",
          named: "never",
          asyncArrow: "always",
        },
      ],
    },
  },
  globalIgnores(["build/**"]),
]);


================================================
FILE: apps/vscode/ext-src/extension.ts
================================================
import * as path from "path";
import * as vscode from "vscode";
import { createWebviewPanel } from "./webview";

function getPanelTitle(document?: vscode.TextDocument) {
  if (!document) return "JSON Crack";

  const fileName = path.basename(document.fileName);
  return fileName || "JSON Crack";
}

export function activate(context: vscode.ExtensionContext) {
  context.subscriptions.push(
    vscode.commands.registerCommand("jsoncrack-vscode.start", () =>
      createWebviewForActiveEditor(context)
    ),
    vscode.commands.registerCommand("jsoncrack-vscode.start.specific", (content?: string) =>
      createWebviewForContent(context, content)
    ),
    vscode.commands.registerCommand("jsoncrack-vscode.start.selected", () =>
      createWebviewForSelectedText(context)
    )
  );
}

// create webview for selected text
async function createWebviewForSelectedText(context: vscode.ExtensionContext) {
  const editor = vscode.window.activeTextEditor;

  if (editor && editor.selection.isEmpty) {
    vscode.window.showInformationMessage("Please select some text first!");
    return;
  }

  const selectedText = editor?.document.getText(editor.selection);

  // Create the webview panel and send the selected JSON content
  const panel = createWebviewPanel(context, getPanelTitle(editor?.document));
  panel.webview.postMessage({
    json: selectedText,
  });

  const onReceiveMessage = panel.webview.onDidReceiveMessage(e => {
    if (e === "ready") {
      panel.webview.postMessage({
        json: selectedText,
      });
    }
  });

  const onTextChange = vscode.workspace.onDidChangeTextDocument(changeEvent => {
    if (changeEvent.document === editor?.document) {
      panel.webview.postMessage({
        json: changeEvent.document.getText(editor?.selection),
      });
    }
  });

  const disposer = () => {
    onTextChange.dispose();
    onReceiveMessage.dispose();
  };

  panel.onDidDispose(disposer, null, context.subscriptions);
}

async function createWebviewForActiveEditor(context: vscode.ExtensionContext) {
  const editor = vscode.window.activeTextEditor;
  const panel = createWebviewPanel(context, getPanelTitle(editor?.document));

  const onReceiveMessage = panel.webview.onDidReceiveMessage(e => {
    if (e === "ready") {
      panel.webview.postMessage({
        json: editor?.document.getText(),
      });
    }
  });

  const onTextChange = vscode.workspace.onDidChangeTextDocument(changeEvent => {
    if (changeEvent.document === editor?.document) {
      panel.webview.postMessage({
        json: changeEvent.document.getText(),
      });
    }
  });

  const disposer = () => {
    onTextChange.dispose();
    onReceiveMessage.dispose();
  };

  panel.onDidDispose(disposer, null, context.subscriptions);
}

/**
 * Renders a readonly diagram from a string
 * @param context ExtensionContext
 * @param content JSON content as a string
 */
function createWebviewForContent(context?: vscode.ExtensionContext, content?: string): any {
  if (context && content) {
    const panel = createWebviewPanel(
      context,
      getPanelTitle(vscode.window.activeTextEditor?.document)
    );
    panel.webview.postMessage({
      json: content,
    });
  }
}

// This method is called when your extension is deactivated
export function deactivate() {}


================================================
FILE: apps/vscode/ext-src/webview.ts
================================================
import * as path from "path";
import * as vscode from "vscode";

export function createWebviewPanel(context: vscode.ExtensionContext, title = "JSON Crack") {
  const extPath = context.extensionPath;
  const webviewDir = vscode.Uri.file(path.join(extPath, "build", "webview"));

  const panel = vscode.window.createWebviewPanel(
    "liveHTMLPreviewer",
    title,
    vscode.ViewColumn.Beside,
    {
      enableScripts: true,
      retainContextWhenHidden: true,
      localResourceRoots: [webviewDir, vscode.Uri.file(path.join(extPath, "assets"))],
    }
  );
  panel.iconPath = vscode.Uri.file(path.join(extPath, "assets", "jsoncrack.png"));

  const scriptUri = panel.webview.asWebviewUri(
    vscode.Uri.file(path.join(extPath, "build", "webview", "index.js"))
  );
  const styleUri = panel.webview.asWebviewUri(
    vscode.Uri.file(path.join(extPath, "build", "webview", "index.css"))
  );

  const nonce = getNonce();
  const csp = [
    `default-src 'self' ${panel.webview.cspSource} blob:`,
    `connect-src ${panel.webview.cspSource} blob:`,
    `script-src 'unsafe-eval' 'unsafe-inline' ${panel.webview.cspSource}`,
    `style-src ${panel.webview.cspSource} 'unsafe-inline'`,
    `worker-src ${panel.webview.cspSource} blob: data:`,
  ].join("; ");

  panel.webview.html = `<!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="Content-Security-Policy" content="${csp}">
        <link href="${styleUri}" rel="stylesheet">
      </head>
      <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="root"></div>
        <script nonce="${nonce}" src="${scriptUri}"></script>
      </body>
      </html>`;

  return panel;
}

function getNonce() {
  let text = "";
  const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (let i = 0; i < 32; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}


================================================
FILE: apps/vscode/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script type="module" src="/src/index.tsx"></script>
  </body>
</html>


================================================
FILE: apps/vscode/package.json
================================================
{
  "name": "vscode",
  "version": "3.0.0",
  "displayName": "JSON Crack",
  "description": "Seamlessly visualize your JSON data instantly into graphs.",
  "publisher": "AykutSarac",
  "license": "MIT",
  "author": {
    "email": "aykutsarac0@gmail.com",
    "name": "Aykut Saraç"
  },
  "homepage": "https://jsoncrack.com",
  "icon": "assets/jsoncrack.png",
  "galleryBanner": {
    "color": "#202225",
    "theme": "dark"
  },
  "categories": [
    "Visualization"
  ],
  "keywords": [
    "json",
    "visualizer",
    "jsoncrack",
    "data",
    "yaml",
    "livepreview"
  ],
  "activationEvents": [
    "workspaceContains:**/*.{json}"
  ],
  "main": "./build/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "jsoncrack-vscode.start",
        "title": "Enable JSON Crack visualization",
        "category": "menubar",
        "icon": {
          "light": "./assets/icon-light.svg",
          "dark": "./assets/icon-dark.svg"
        }
      },
      {
        "command": "jsoncrack-vscode.start.specific",
        "title": "Enable JSON Crack visualization for specific file",
        "category": "menubar"
      },
      {
        "command": "jsoncrack-vscode.start.selected",
        "title": "Open with JSON Crack",
        "category": "Navigation"
      }
    ],
    "menus": {
      "editor/context": [
        {
          "command": "jsoncrack-vscode.start.selected",
          "when": "editorHasSelection",
          "group": "navigation"
        }
      ],
      "commandPalette": [
        {
          "command": "jsoncrack-vscode.start",
          "when": "never"
        }
      ],
      "editor/title": [
        {
          "command": "jsoncrack-vscode.start",
          "when": "resourceExtname == .json || editorLangId == json",
          "group": "navigation"
        }
      ]
    }
  },
  "scripts": {
    "vscode:prepublish": "pnpm run build",
    "dev": "vite",
    "build": "vite build && node esbuild.config.mjs --production",
    "build:dev": "vite build && node esbuild.config.mjs",
    "watch": "node esbuild.config.mjs --watch",
    "watch:webview": "vite build --watch",
    "lint": "eslint src ext-src && prettier --check src ext-src",
    "lint:fix": "eslint --fix src ext-src && prettier --write src ext-src",
    "clean": "rm -rf build"
  },
  "devDependencies": {
    "@eslint/js": "^10.0.1",
    "@mantine/code-highlight": "^8.3.18",
    "@mantine/core": "^8.3.18",
    "@mantine/hooks": "^8.3.18",
    "@trivago/prettier-plugin-sort-imports": "^6.0.2",
    "@types/node": "^22.19.15",
    "@types/react": "19.2.11",
    "@types/react-dom": "19.2.3",
    "@types/vscode": "^1.110.0",
    "@typescript-eslint/eslint-plugin": "^8.57.1",
    "@typescript-eslint/parser": "^8.57.1",
    "@vitejs/plugin-react": "^4.7.0",
    "esbuild": "^0.25.12",
    "eslint": "^10.0.3",
    "eslint-config-prettier": "^10.1.8",
    "eslint-plugin-prettier": "^5.5.5",
    "eslint-plugin-unused-imports": "^4.4.1",
    "globals": "^17.4.0",
    "prettier": "^3.8.1",
    "typescript": "^5.9.3",
    "typescript-eslint": "^8.57.1",
    "vite": "^6.4.1"
  },
  "dependencies": {
    "jsoncrack-react": "workspace:*",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "shiki": "^4.0.2"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/AykutSarac/jsoncrack.com"
  },
  "bugs": {
    "url": "https://github.com/AykutSarac/jsoncrack.com/issues"
  },
  "engines": {
    "vscode": "^1.86.0"
  },
  "packageManager": "pnpm@10.20.0"
}


================================================
FILE: apps/vscode/src/App.tsx
================================================
import { useCallback, useEffect, useState } from "react";
import { Anchor, Box, MantineProvider, Text } from "@mantine/core";
import { CodeHighlightAdapterProvider, createShikiAdapter } from "@mantine/code-highlight";
import type { NodeData } from "jsoncrack-react";
import { JSONCrack } from "jsoncrack-react";
import { NodeModal } from "./components/NodeModal";

async function loadShiki() {
  const { createHighlighter } = await import("shiki");
  return createHighlighter({ langs: ["json"], themes: [] });
}

const shikiAdapter = createShikiAdapter(loadShiki);

function getTheme() {
  const theme = document.body.getAttribute("data-vscode-theme-kind");
  if (theme?.includes("light")) return "light" as const;
  return "dark";
}

const App: React.FC = () => {
  const [json, setJson] = useState("{}");
  const [selectedNode, setSelectedNode] = useState<NodeData | null>(null);
  const theme = getTheme();

  useEffect(() => {
    const vscode = window?.acquireVsCodeApi?.();
    vscode?.postMessage("ready");

    const onMessage = (event: MessageEvent<{ json?: string }>) => {
      const jsonData = event.data?.json;
      if (typeof jsonData === "string") {
        setJson(jsonData);
      }
    };

    window.addEventListener("message", onMessage);

    return () => {
      window.removeEventListener("message", onMessage);
    };
  }, []);

  const handleNodeClick = useCallback((node: NodeData) => {
    setSelectedNode(node);
  }, []);

  const closeNodeModal = useCallback(() => {
    setSelectedNode(null);
  }, []);

  return (
    <MantineProvider forceColorScheme={theme}>
      <CodeHighlightAdapterProvider adapter={shikiAdapter}>
        <Box h="100vh" w="100vw">
          <JSONCrack json={json} theme={theme} showControls={false} onNodeClick={handleNodeClick} />
          {selectedNode && (
            <NodeModal opened={!!selectedNode} onClose={closeNodeModal} nodeData={selectedNode} />
          )}
          <Anchor
            pos="fixed"
            bottom={0}
            left={0}
            href="https://jsoncrack.com/editor?utm_source=vscode&utm_campaign=attribute"
            target="_blank"
          >
            <Box px="12" py="4" bg="dark">
              <Text fz="sm" c="white">
                Powered by JSON Crack
              </Text>
            </Box>
          </Anchor>
        </Box>
      </CodeHighlightAdapterProvider>
    </MantineProvider>
  );
};

export default App;

declare global {
  interface Window {
    acquireVsCodeApi?: () => any;
  }
}


================================================
FILE: apps/vscode/src/components/NodeModal.tsx
================================================
import type { ModalProps } from "@mantine/core";
import { Modal, Stack, Text, ScrollArea } from "@mantine/core";
import { CodeHighlight } from "@mantine/code-highlight";
import type { NodeData } from "jsoncrack-react";

interface NodeModalProps extends ModalProps {
  nodeData: NodeData | null;
}

const normalizeNodeData = (nodeRows: NodeData["text"]) => {
  if (!nodeRows || nodeRows.length === 0) return "{}";
  if (nodeRows.length === 1 && !nodeRows[0].key) return `${nodeRows[0].value}`;

  const obj: Record<string, unknown> = {};
  nodeRows.forEach(row => {
    if (row.type !== "array" && row.type !== "object" && row.key) {
      obj[row.key] = row.value;
    }
  });

  return JSON.stringify(obj, null, 2);
};

const jsonPathToString = (path?: NodeData["path"]) => {
  if (!path || path.length === 0) return "$";
  const segments = path.map(seg => (typeof seg === "number" ? seg : `"${seg}"`));
  return `$[${segments.join("][")}]`;
};

export const NodeModal = ({ opened, onClose, nodeData }: NodeModalProps) => {
  const nodeContent = normalizeNodeData(nodeData?.text ?? []);
  const jsonPath = jsonPathToString(nodeData?.path);

  return (
    <Modal title="Node Content" size="auto" opened={opened} onClose={onClose} centered>
      <Stack py="sm" gap="sm">
        <Stack gap="xs">
          <Text fz="xs" fw={500}>
            Content
          </Text>
          <ScrollArea.Autosize mah={250} maw={600}>
            <CodeHighlight code={nodeContent} miw={350} maw={600} language="json" withCopyButton />
          </ScrollArea.Autosize>
        </Stack>
        <Text fz="xs" fw={500}>
          JSON Path
        </Text>
        <ScrollArea.Autosize maw={600}>
          <CodeHighlight
            code={jsonPath}
            miw={350}
            mah={250}
            language="json"
            copyLabel="Copy to clipboard"
            copiedLabel="Copied to clipboard"
            withCopyButton
          />
        </ScrollArea.Autosize>
      </Stack>
    </Modal>
  );
};


================================================
FILE: apps/vscode/src/env.d.ts
================================================
/// <reference types="vite/client" />


================================================
FILE: apps/vscode/src/global.css
================================================
html,
body,
#root {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

a {
  color: inherit;
  text-decoration: none;
}


================================================
FILE: apps/vscode/src/index.tsx
================================================
import "@mantine/core/styles.css";
import "@mantine/code-highlight/styles.css";
import "jsoncrack-react/style.css";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./global.css";

const container = document.getElementById("root") as HTMLElement;
const root = createRoot(container);
root.render(<App />);


================================================
FILE: apps/vscode/tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "noImplicitAny": false
  },
  "include": ["src", "ext-src"],
  "exclude": ["node_modules"]
}


================================================
FILE: apps/vscode/vite.config.ts
================================================
import react from "@vitejs/plugin-react";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig } from "vite";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export default defineConfig({
  plugins: [react()],
  build: {
    outDir: "build/webview",
    emptyOutDir: true,
    rollupOptions: {
      input: "src/index.tsx",
      output: {
        entryFileNames: "index.js",
        assetFileNames: "index[extname]",
        inlineDynamicImports: true,
      },
    },
  },
  resolve: {
    alias: {
      react: path.resolve(__dirname, "node_modules/react"),
      "react-dom": path.resolve(__dirname, "node_modules/react-dom"),
    },
  },
});


================================================
FILE: apps/www/.dockerignore
================================================
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git

================================================
FILE: apps/www/.gitignore
================================================
# App dependencies
node_modules/

# Next.js
.next/
out/

# Turborepo task cache
.turbo/

# Test/build artifacts
coverage/
build/
*.tsbuildinfo

# Local environment files
.env.local
.env.development.local
.env.test.local
.env.production.local

# Deployment
.vercel

# Workspace migration artifact (root lockfile is authoritative)
pnpm-lock.yaml

# PWA workers
public/workbox-*.js
public/sw.js
public/fallback-*.js


================================================
FILE: apps/www/.prettierignore
================================================
.github
.next
node_modules/
out
public
*-lock.json
tsconfig.json

================================================
FILE: apps/www/.prettierrc
================================================
{
  "trailingComma": "es5",
  "singleQuote": false,
  "semi": true,
  "printWidth": 100,
  "arrowParens": "avoid",
  "importOrder": [
    "^(react/(.*)$)|^(react$)",
    "^(next/(.*)$)|^(next$)",
    "^@mantine/core",
    "^@mantine",
    "styled",
    "<THIRD_PARTY_MODULES>",
    "^src/(.*)$",
    "^[./]"
  ],
  "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"],
  "plugins": ["@trivago/prettier-plugin-sort-imports"]
}


================================================
FILE: apps/www/Dockerfile
================================================
FROM node:24.10.0-alpine AS base
RUN npm install -g pnpm@10.10.0

# Stage 1: Install dependencies
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile

# Stage 2: Build the application
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable pnpm && pnpm run build

# Stage 3: Production image
FROM nginxinc/nginx-unprivileged:stable AS production
WORKDIR /app
COPY --from=builder /app/out /app
COPY ./nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 8080


================================================
FILE: apps/www/LICENSE.md
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2025 Aykut Saraç

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: apps/www/docker-compose.yml
================================================
services:
  jsoncrack:
    image: jsoncrack
    container_name: jsoncrack
    build: 
      context: .
      dockerfile: Dockerfile
    ports:
      - "8888:8080"
    environment:
      - NODE_ENV=production

================================================
FILE: apps/www/eslint.config.mjs
================================================
import eslint from "@eslint/js";
import nextPlugin from "@next/eslint-plugin-next";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import eslintPluginPrettier from "eslint-plugin-prettier/recommended";
import unusedImports from "eslint-plugin-unused-imports";
import { defineConfig, globalIgnores } from "eslint/config";
import tseslint from "typescript-eslint";

export default defineConfig([
  globalIgnores(["src/enums", "**/next.config.js", "src/lib/utils/json2go.js"]),
  eslint.configs.recommended,
  tseslint.configs.recommended,
  {
    plugins: {
      "@next/next": nextPlugin,
    },
    rules: {
      ...nextPlugin.configs.recommended.rules,
      ...nextPlugin.configs["core-web-vitals"].rules,
      "@next/next/no-img-element": "off",
    },
  },
  eslintConfigPrettier,
  eslintPluginPrettier,
  {
    languageOptions: {
      parserOptions: {
        tsconfigRootDir: import.meta.dirname,
      },
    },
    plugins: {
      "unused-imports": unusedImports,
    },
    rules: {
      "@typescript-eslint/consistent-type-imports": "error",
      "unused-imports/no-unused-imports": "error",
      "@typescript-eslint/no-explicit-any": "off",
      "prettier/prettier": "error",
      "space-in-parens": "error",
      "no-empty": "error",
      "no-multiple-empty-lines": "error",
      "no-irregular-whitespace": "error",
      strict: ["error", "never"],
      "linebreak-style": ["error", "unix"],
      quotes: ["error", "double", { avoidEscape: true }],
      semi: ["error", "always"],
      "prefer-const": "error",
      "space-before-function-paren": [
        "error",
        {
          anonymous: "always",
          named: "never",
          asyncArrow: "always",
        },
      ],
    },
  },
]);


================================================
FILE: apps/www/next-env.d.ts
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.


================================================
FILE: apps/www/next-sitemap.config.js
================================================
/** @type {import('next-sitemap').IConfig} */
module.exports = {
  siteUrl: "https://jsoncrack.com",
  exclude: ["/widget"],
  autoLastmod: false,
  changefreq: "never",
};


================================================
FILE: apps/www/next.config.js
================================================
const withBundleAnalyzer = require("@next/bundle-analyzer")({
  enabled: process.env.ANALYZE === "true",
});

/**
 * @type {import('next').NextConfig}
 */
const config = {
  output: "export",
  transpilePackages: ["jsoncrack"],
  reactStrictMode: false,
  productionBrowserSourceMaps: true,
  compiler: {
    styledComponents: true,
  },
  turbopack: {
    resolveAlias: {
      fs: {
        browser: "./shims/empty.ts",
      },
    },
  },
  webpack: (config, { isServer }) => {
    config.resolve.fallback = { fs: false };
    config.output.webassemblyModuleFilename = "static/wasm/[modulehash].wasm";
    config.experiments = { asyncWebAssembly: true, layers: true };

    if (!isServer) {
      config.output.environment = { ...config.output.environment, asyncFunction: true };
    }

    return config;
  },
};

const configExport = () => {
  if (process.env.ANALYZE === "true") return withBundleAnalyzer(config);
  return config;
};

module.exports = configExport();


================================================
FILE: apps/www/nginx.conf
================================================
server {
    listen 8080;
    root  /app;
    include /etc/nginx/mime.types;

    location /editor {
        try_files $uri /editor.html;
    }
    
    location /widget {
        try_files $uri /widget.html;
    }

    location /docs {
        try_files $uri /docs.html;
    }
}


================================================
FILE: apps/www/package.json
================================================
{
  "name": "www",
  "private": true,
  "version": "0.0.0",
  "license": "Apache-2.0",
  "scripts": {
    "dev": "next dev --webpack",
    "build": "next build --webpack",
    "postbuild": "next-sitemap --config next-sitemap.config.js",
    "start": "next start",
    "lint": "tsc --project tsconfig.json && eslint src && prettier --check src",
    "lint:fix": "eslint --fix src && prettier --write src",
    "analyze": "ANALYZE=true npm run build"
  },
  "dependencies": {
    "@mantine/code-highlight": "^8.3.18",
    "@mantine/core": "^8.3.18",
    "@mantine/dropzone": "^8.3.18",
    "@mantine/hooks": "^8.3.18",
    "@monaco-editor/react": "^4.7.0",
    "allotment": "^1.20.5",
    "fast-xml-parser": "5.5.6",
    "gofmt.js": "0.0.2",
    "html-to-image": "1.11.13",
    "jq-web": "0.6.2",
    "js-yaml": "4.1.1",
    "json-2-csv": "5.5.10",
    "json-schema-faker": "0.6.0",
    "json_typegen_wasm": "0.7.0",
    "jsonc-parser": "3.3.1",
    "jsoncrack-react": "workspace:*",
    "jsonpath-plus": "10.4.0",
    "lodash.debounce": "^4.0.8",
    "next": "16.1.7",
    "next-seo": "^7.2.0",
    "next-sitemap": "^4.2.3",
    "nextjs-google-analytics": "^2.3.7",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "react-hot-toast": "^2.6.0",
    "react-icons": "^5.6.0",
    "react-json-tree": "^0.20.0",
    "react-linkify-it": "^2.0.0",
    "react-zoomable-ui": "^0.11.0",
    "reaflow": "5.4.1",
    "shiki": "^4.0.2",
    "styled-components": "^6.3.11",
    "use-long-press": "^3.3.0",
    "zustand": "^5.0.12"
  },
  "devDependencies": {
    "@eslint/js": "^10.0.1",
    "@next/bundle-analyzer": "16.1.7",
    "@next/eslint-plugin-next": "16.1.7",
    "@trivago/prettier-plugin-sort-imports": "^6.0.2",
    "@types/js-yaml": "^4.0.9",
    "@types/node": "^25.5.0",
    "@types/react": "19.2.14",
    "@types/react-dom": "19.2.3",
    "eslint": "^10.0.3",
    "eslint-config-prettier": "^10.1.8",
    "eslint-plugin-prettier": "^5.5.5",
    "eslint-plugin-unused-imports": "^4.4.1",
    "prettier": "^3.8.1",
    "ts-node": "^10.9.2",
    "typescript": "5.9.3",
    "typescript-eslint": "^8.57.1"
  },
  "packageManager": "pnpm@10.20.0"
}


================================================
FILE: apps/www/public/.nojekyll
================================================


================================================
FILE: apps/www/public/CNAME
================================================
jsoncrack.com

================================================
FILE: apps/www/public/manifest.json
================================================
{
  "name": "JSON Crack",
  "short_name": "JSON Crack",
  "description": "JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.",
  "theme_color": "#36393e",
  "background_color": "#36393e",
  "display": "standalone",
  "orientation": "landscape",
  "scope": "/editor",
  "start_url": "/editor",
  "icons": [
    {
      "src": "assets/192.png",
      "sizes": "192x192",
      "type": "image/png"
    },

    {
      "src": "assets/512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}


================================================
FILE: apps/www/public/robots.txt
================================================
User-agent: *

Allow: /

Sitemap: https://jsoncrack.com/sitemap.xml


================================================
FILE: apps/www/public/sitemap-0.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<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">
<url><loc>https://jsoncrack.com</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/csv-to-json</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/csv-to-xml</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/csv-to-yaml</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/json-to-csv</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/json-to-xml</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/json-to-yaml</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/xml-to-csv</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/xml-to-json</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/xml-to-yaml</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/yaml-to-csv</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/yaml-to-json</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/converter/yaml-to-xml</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/docs</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/editor</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/legal/privacy</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/legal/terms</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/tools/json-schema</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/csv-to-go</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/csv-to-kotlin</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/csv-to-rust</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/csv-to-typescript</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/json-to-go</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/json-to-kotlin</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/json-to-rust</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/json-to-typescript</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/xml-to-go</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/xml-to-kotlin</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/xml-to-rust</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/xml-to-typescript</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/yaml-to-go</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/yaml-to-kotlin</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/yaml-to-rust</loc><changefreq>never</changefreq><priority>0.7</priority></url>
<url><loc>https://jsoncrack.com/type/yaml-to-typescript</loc><changefreq>never</changefreq><priority>0.7</priority></url>
</urlset>

================================================
FILE: apps/www/public/sitemap.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://jsoncrack.com/sitemap-0.xml</loc></sitemap>
</sitemapindex>

================================================
FILE: apps/www/shims/empty.ts
================================================
export {};


================================================
FILE: apps/www/src/constants/globalStyle.ts
================================================
import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  html, body {
    background: #ffffff;
    overscroll-behavior: none;
    -webkit-font-smoothing: subpixel-antialiased !important;
  }

  *,
  *::before,
  *::after {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      scroll-behavior: smooth !important;
      -webkit-tap-highlight-color: transparent;
      -webkit-font-smoothing: never;
  }

  .hide {
    display: none;
  }

  svg {
    vertical-align: text-top;
  }

  a {
    color: unset;
    text-decoration: none;
  }

  button {
    border: none;
    outline: none;
    background: transparent;
    width: fit-content;
    margin: 0;
    padding: 0;
    cursor: pointer;
  }
`;

export default GlobalStyle;


================================================
FILE: apps/www/src/constants/graph.ts
================================================
export const NODE_DIMENSIONS = {
  ROW_HEIGHT: 30, // Regular row height
  PARENT_HEIGHT: 36, // Height for parent nodes
} as const;

export const SUPPORTED_LIMIT = +(process.env.NEXT_PUBLIC_NODE_LIMIT as string);


================================================
FILE: apps/www/src/constants/seo.ts
================================================
import type { DefaultSeoProps } from "next-seo/pages";

export const SEO: DefaultSeoProps = {
  title: "JSON Crack | Online JSON Viewer - Transform your data into interactive graphs",
  description:
    "JSON Crack Editor is a tool for visualizing into graphs, analyzing, editing, formatting, querying, transforming and validating JSON, CSV, YAML, XML, and more.",
  themeColor: "#36393E",
  openGraph: {
    type: "website",
    images: [
      {
        url: "https://jsoncrack.com/assets/jsoncrack.png",
        width: 1200,
        height: 627,
      },
    ],
  },
  twitter: {
    handle: "@jsoncrack",
    cardType: "summary_large_image",
  },
  additionalLinkTags: [
    {
      rel: "manifest",
      href: "/manifest.json",
    },
    {
      rel: "icon",
      href: "/favicon.ico",
      sizes: "48x48",
    },
  ],
};


================================================
FILE: apps/www/src/constants/theme.ts
================================================
const fixedColors = {
  CRIMSON: "#DC143C",
  BLURPLE: "#5865F2",
  PURPLE: "#9036AF",
  FULL_WHITE: "#FFFFFF",
  BLACK: "#202225",
  BLACK_DARK: "#2C2F33",
  BLACK_LIGHT: "#2F3136",
  BLACK_PRIMARY: "#36393f",
  DARK_SALMON: "#E9967A",
  DANGER: "hsl(359,calc(var(--saturation-factor, 1)*66.7%),54.1%)",
  LIGHTGREEN: "#90EE90",
  SEAGREEN: "#11883B",
  ORANGE: "#FAA81A",
  SILVER: "#B9BBBE",
  PRIMARY: "#4D4D4D",
  TEXT_DANGER: "#db662e",
};

const nodeColors = {
  dark: {
    NODE_COLORS: {
      TEXT: "#DCE5E7",
      NODE_KEY: "#59b8ff",
      NODE_VALUE: "#DCE5E7",
      INTEGER: "#e8c479",
      NULL: "#939598",
      BOOL: {
        FALSE: "#F85C50",
        TRUE: "#00DC7D",
      },
      PARENT_ARR: "#FC9A40",
      PARENT_OBJ: "#59b8ff",
      CHILD_COUNT: "white",
      DIVIDER: "#383838",
    },
  },
  light: {
    NODE_COLORS: {
      TEXT: "#000",
      NODE_KEY: "#761CEA",
      NODE_VALUE: "#535353",
      INTEGER: "#FD0079",
      NULL: "#afafaf",
      BOOL: {
        FALSE: "#FF0000",
        TRUE: "#748700",
      },
      PARENT_ARR: "#FF6B00",
      PARENT_OBJ: "#761CEA",
      CHILD_COUNT: "#535353",
      DIVIDER: "#e6e6e6",
    },
  },
};

export const darkTheme = {
  ...fixedColors,
  ...nodeColors.dark,
  BLACK_SECONDARY: "#23272A",
  SILVER_DARK: "#4D4D4D",
  NODE_KEY: "#FAA81A",
  OBJECT_KEY: "#59b8ff",
  SIDEBAR_ICONS: "#8B8E90",

  INTERACTIVE_NORMAL: "#b9bbbe",
  INTERACTIVE_HOVER: "#dcddde",
  INTERACTIVE_ACTIVE: "#fff",
  BACKGROUND_NODE: "#2B2C3E",
  BACKGROUND_TERTIARY: "#202225",
  BACKGROUND_SECONDARY: "#2f3136",
  TOOLBAR_BG: "#262626",
  BACKGROUND_PRIMARY: "#36393f",
  BACKGROUND_MODIFIER_ACCENT: "rgba(79,84,92,0.48)",
  MODAL_BACKGROUND: "#36393E",
  TEXT_NORMAL: "#dcddde",
  TEXT_POSITIVE: "hsl(139,calc(var(--saturation-factor, 1)*51.6%),52.2%)",
  GRID_BG_COLOR: "#141414",
  GRID_COLOR_PRIMARY: "#1c1b1b",
  GRID_COLOR_SECONDARY: "#191919",
};

export const lightTheme = {
  ...fixedColors,
  ...nodeColors.light,
  BLACK_SECONDARY: "#F2F2F2",
  SILVER_DARK: "#CCCCCC",
  NODE_KEY: "#DC3790",
  OBJECT_KEY: "#0260E8",
  SIDEBAR_ICONS: "#6D6E70",

  INTERACTIVE_NORMAL: "#4f5660",
  INTERACTIVE_HOVER: "#2e3338",
  INTERACTIVE_ACTIVE: "#060607",
  BACKGROUND_NODE: "#F6F8FA",
  BACKGROUND_TERTIARY: "#e3e5e8",
  BACKGROUND_SECONDARY: "#f2f3f5",
  TOOLBAR_BG: "#ECECEC",
  BACKGROUND_PRIMARY: "#FFFFFF",
  BACKGROUND_MODIFIER_ACCENT: "rgba(106,116,128,0.24)",
  MODAL_BACKGROUND: "#FFFFFF",
  TEXT_NORMAL: "#2e3338",
  TEXT_POSITIVE: "#008736",
  GRID_BG_COLOR: "#f7f7f7",
  GRID_COLOR_PRIMARY: "#ebe8e8",
  GRID_COLOR_SECONDARY: "#f2eeee",
};

const themeDs = {
  ...lightTheme,
  ...darkTheme,
};

export default themeDs;


================================================
FILE: apps/www/src/data/example.json
================================================
{
  "fruits": [
    {
      "name": "Apple",
      "color": "#FF0000",
      "details": {
        "type": "Pome",
        "season": "Fall"
      },
      "nutrients": {
        "calories": 52,
        "fiber": "2.4g",
        "vitaminC": "4.6mg"
      }
    },
    {
      "name": "Banana",
      "color": "#FFFF00",
      "details": {
        "type": "Berry",
        "season": "Year-round"
      },
      "nutrients": {
        "calories": 89,
        "fiber": "2.6g",
        "potassium": "358mg"
      }
    },
    {
      "name": "Orange",
      "color": "#FFA500",
      "details": {
        "type": "Citrus",
        "season": "Winter"
      },
      "nutrients": {
        "calories": 47,
        "fiber": "2.4g",
        "vitaminC": "53.2mg"
      }
    }
  ]
}


================================================
FILE: apps/www/src/data/faq.json
================================================
[
  {
    "title": "What is JSON Crack and what does it do?",
    "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."
  },
  {
    "title": "How is it different than traditional JSON viewers?",
    "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."
  },
  {
    "title": "Is JSON Crack free?",
    "content": "Yes, JSON Crack is a free-forever open source online tool. For advanced features you may use ToDiagram.com"
  },
  {
    "title": "Is my data secure?",
    "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."
  },
  {
    "title": "Can I convert JSON to other formats using JSON Crack?",
    "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."
  },
  {
    "title": "What kind of data formats are supported?",
    "content": "A wide range of data formats are supported including JSON, YAML, XML, and CSV."
  },
  {
    "title": "What size of data can I visualize?",
    "content": "It supports approximately 300 KB. It might vary depending on the complexity of the data and your hardware."
  },
  {
    "title": "Can I export the generated graphs?",
    "content": "Yes, you can export the generated graphs as PNG, JPEG, or SVG files."
  },
  {
    "title": "How to use VS Code extension?",
    "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."
  },
  {
    "title": "I've previously subscribed to the premium plan, where did it go?",
    "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."
  }
]


================================================
FILE: apps/www/src/data/privacy.json
================================================
{
  "Introduction": [
    "Welcome to JSON Crack.",
    "JSON Crack (“us”, “we”, or “our”) operates https://jsoncrack.com (hereinafter referred to as “Service”).",
    "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.",
    "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.",
    "Our Terms and Conditions (“Terms”) govern all use of our Service and together with the Privacy Policy constitute your agreement with us (“agreement”)."
  ],
  "Definitions": [
    "SERVICE means the https://jsoncrack.com website.",
    "PERSONAL DATA means data about a living individual who can be identified from those data.",
    "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).",
    "COOKIES are small files stored on your device (computer or mobile device)."
  ],
  "Information Collection and Use": [
    "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."
  ],
  "Data Collection": [
    "Usage Data",
    "• We may collect information that your browser sends whenever you visit our Service (“Usage Data”).",
    "• 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.",
    "Cookies",
    "• We use cookies and similar tracking technologies to monitor activity on our Service.",
    "• Cookies are files with a small amount of data that may include an anonymous unique identifier."
  ],
  "Use of Data": [
    "JSON Crack uses collected data to:",
    "• Provide and maintain the Service;",
    "• Improve and analyze Service performance."
  ],
  "Security of Data": [
    "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.",
    "Usage Data may be shared with third-party analytics services as outlined below."
  ],
  "Analytics": [
    "We may use Google Analytics to monitor Service usage.",
    "• Google Analytics collects non-personally identifying information such as browser type, referring pages, and time spent on the site.",
    "• We do not collect IP addresses through Google Analytics.",
    "• Learn more about Google's privacy practices: https://support.google.com/analytics/answer/4597324?hl=en"
  ],
  "Changes to This Privacy Policy": [
    "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.",
    "You are advised to review this Privacy Policy periodically for any changes. Changes are effective when posted on this page."
  ],
  "Contact Us": [
    "If you have any questions about this Privacy Policy, please contact us at contact@todiagram.com."
  ]
}


================================================
FILE: apps/www/src/data/terms.json
================================================
{
  "Introduction": [
    "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.",
    "These Terms of Service govern your use of our web pages located at https://jsoncrack.com.",
    "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.",
    "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.",
    "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.",
    "Thank you for being responsible."
  ],
  "Prohibited Uses": [
    "You may use Service only for lawful purposes and in accordance with Terms. You agree not to use Service:",
    "• In any way that violates any applicable national or international law or regulation.",
    "• For the purpose of exploiting, harming, or attempting to exploit or harm minors in any way by exposing them to inappropriate content or otherwise.",
    "• To transmit, or procure the sending of, any advertising or promotional material, including any “junk mail”, “chain letter,” “spam,” or any other similar solicitation.",
    "• 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.",
    "• 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.",
    "Additionally, you agree not to:",
    "• 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.",
    "• 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.",
    "• 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.",
    "• Use any device, software, or routine that interferes with the proper working of Service.",
    "• Introduce any viruses, trojan horses, worms, logic bombs, or other material which is malicious or technologically harmful.",
    "• 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.",
    "• Attack Service via a denial-of-service attack or a distributed denial-of-service attack.",
    "• Take any action that may damage or falsify Company rating.",
    "• Otherwise attempt to interfere with the proper working of Service."
  ],
  "Analytics": [
    "We may use third-party Service Providers to monitor and analyze the use of our Service.",
    "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.",
    "For more information on the privacy practices of Google, please visit the Google Privacy Terms web page: https://policies.google.com/privacy?hl=en",
    "We also encourage you to review the Google's policy for safeguarding your data: https://support.google.com/analytics/answer/6004245"
  ],
  "Data Security": [
    "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."
  ],
  "Intellectual Property": [
    "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."
  ],
  "Disclaimer of Warranty": [
    "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.",
    "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.",
    "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.",
    "THE FOREGOING DOES NOT AFFECT ANY WARRANTIES WHICH CANNOT BE EXCLUDED OR LIMITED UNDER APPLICABLE LAW."
  ],
  "Limitation of Liability": [
    "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."
  ],
  "Termination": [
    "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.",
    "If you wish to terminate your account, you may simply discontinue using Service.",
    "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."
  ],
  "Changes To Service": [
    "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."
  ],
  "Amendments To Terms": [
    "We may amend Terms at any time by posting the amended terms on this site. It is your responsibility to review these Terms periodically.",
    "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.",
    "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."
  ],
  "Acknowledgement": [
    "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."
  ],
  "Contact Us": [
    "If you have any questions about these terms of service, please contact us:",
    "By email: contact@todiagram.com."
  ]
}


================================================
FILE: apps/www/src/enums/file.enum.ts
================================================
export enum FileFormat {
  "JSON" = "json",
  "YAML" = "yaml",
  "XML" = "xml",
  "CSV" = "csv",
}

export const formats = [
  { value: FileFormat.JSON, label: "JSON" },
  { value: FileFormat.YAML, label: "YAML" },
  { value: FileFormat.XML, label: "XML" },
  { value: FileFormat.CSV, label: "CSV" },
];

export enum TypeLanguage {
  TypeScript = "typescript",
  TypeScript_Combined = "typescript/typealias",
  Go = "go",
  JSON_SCHEMA = "json_schema",
  Kotlin = "kotlin",
  Rust = "rust",
}

export const typeOptions = [
  {
    label: "TypeScript",
    value: TypeLanguage.TypeScript,
    lang: "typescript",
  },
  {
    label: "TypeScript (merged)",
    value: TypeLanguage.TypeScript_Combined,
    lang: "typescript",
  },
  {
    label: "Go",
    value: TypeLanguage.Go,
    lang: "go",
  },
  {
    label: "JSON Schema",
    value: TypeLanguage.JSON_SCHEMA,
    lang: "json",
  },
  {
    label: "Kotlin",
    value: TypeLanguage.Kotlin,
    lang: "kotlin",
  },
  {
    label: "Rust",
    value: TypeLanguage.Rust,
    lang: "rust",
  },
];


================================================
FILE: apps/www/src/enums/viewMode.enum.ts
================================================
export enum ViewMode {
  Graph = "graph",
  Tree = "tree",
}


================================================
FILE: apps/www/src/features/Banner.tsx
================================================
import React, { useEffect, useState } from "react";
import { Anchor, Flex, Button, ActionIcon } from "@mantine/core";
import { useSessionStorage } from "@mantine/hooks";
import { MdClose } from "react-icons/md";

export const BANNER_HEIGHT =
  process.env.NEXT_PUBLIC_DISABLE_EXTERNAL_MODE === "true" ? "0px" : "40px";

const BANNER_LIST = [
  "Save and store your diagrams with ToDiagram",
  "Explore the ToDiagram from the creators of JSON Crack",
  "Generate AI diagrams with single prompt",
  "Try ToDiagram for free, no sign-up required",
  "Edit data directly inside diagrams",
  "Explore larger datasets (up to 50 MB) easily",
];

export const Banner = () => {
  const ROTATION_INTERVAL = 6000; // ms between label changes
  const FADE_DURATION = 500; // ms for fade transition

  const [index, setIndex] = useState(0);
  const [visible, setVisible] = useState(true);
  const [dismissed, setDismissed] = useSessionStorage({
    key: "jsoncrack_banner_dismissed",
    defaultValue: false,
  });

  useEffect(() => {
    if (dismissed) return;

    let fadeTimeout: ReturnType<typeof setTimeout> | undefined;
    const intervalId = setInterval(() => {
      setVisible(false);
      fadeTimeout = setTimeout(() => {
        setIndex(i => (i + 1) % BANNER_LIST.length);
        setVisible(true);
      }, FADE_DURATION);
    }, ROTATION_INTERVAL);

    return () => {
      clearInterval(intervalId);
      if (fadeTimeout) clearTimeout(fadeTimeout);
    };
  }, [dismissed]);

  const handleDismiss = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    setDismissed(true);
  };

  if (dismissed) return null;

  return (
    <Anchor
      href="https://todiagram.com/editor?utm_source=jsoncrack&utm_medium=top_banner"
      target="_blank"
      rel="noopener"
      underline="never"
      style={{ position: "relative" }}
    >
      <Flex
        h={BANNER_HEIGHT}
        justify="center"
        align="center"
        fw="500"
        gap="xs"
        style={{
          background: "linear-gradient(90deg, #FF75B7 0%, #FED761 100%)",
          color: "black",
        }}
      >
        <span
          style={{
            transition: `opacity ${FADE_DURATION}ms ease`,
            opacity: visible ? 1 : 0,
            willChange: "opacity",
            display: "inline-block",
          }}
        >
          {BANNER_LIST[index]}{" "}
        </span>
        <Button size="xs" color="gray">
          Try now
        </Button>
        <ActionIcon
          onClick={handleDismiss}
          size="sm"
          variant="transparent"
          style={{
            position: "absolute",
            right: "8px",
            color: "black",
          }}
          aria-label="Close banner"
        >
          <MdClose size={18} />
        </ActionIcon>
      </Flex>
    </Anchor>
  );
};


================================================
FILE: apps/www/src/features/editor/BottomBar.tsx
================================================
import React from "react";
import { Flex, Menu, Popover, Text } from "@mantine/core";
import styled from "styled-components";
import { event as gaEvent } from "nextjs-google-analytics";
import { BiSolidDockLeft } from "react-icons/bi";
import { IoMdCheckmark } from "react-icons/io";
import { MdArrowUpward } from "react-icons/md";
import { VscCheck, VscError, VscRunAll, VscSync, VscSyncIgnored } from "react-icons/vsc";
import { formats } from "../../enums/file.enum";
import useConfig from "../../store/useConfig";
import useFile from "../../store/useFile";
import useGraph from "./views/GraphView/stores/useGraph";

const StyledBottomBar = styled.div`
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
  background: ${({ theme }) => theme.TOOLBAR_BG};
  max-height: 27px;
  height: 27px;
  z-index: 35;
  padding-right: 6px;

  @media screen and (max-width: 320px) {
    display: none;
  }
`;

const StyledLeft = styled.div`
  display: flex;
  align-items: center;
  justify-content: left;
  gap: 4px;
  padding-left: 8px;

  @media screen and (max-width: 480px) {
    display: none;
  }
`;

const StyledRight = styled.div`
  display: flex;
  align-items: center;
  justify-content: right;
  gap: 4px;
`;

const StyledBottomBarItem = styled.button<{ $bg?: string }>`
  display: flex;
  align-items: center;
  gap: 4px;
  width: fit-content;
  margin: 0;
  height: 28px;
  padding: 4px;
  font-size: 12px;
  font-weight: 400;
  color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
  background: ${({ $bg }) => $bg};
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;

  &:hover:not(&:disabled) {
    background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);
    color: ${({ theme }) => theme.INTERACTIVE_HOVER};
  }

  &:disabled {
    opacity: 0.6;
    cursor: default;
  }
`;

export const BottomBar = () => {
  const data = useFile(state => state.fileData);
  const toggleLiveTransform = useConfig(state => state.toggleLiveTransform);
  const liveTransformEnabled = useConfig(state => state.liveTransformEnabled);
  const error = useFile(state => state.error);
  const setContents = useFile(state => state.setContents);
  const toggleFullscreen = useGraph(state => state.toggleFullscreen);
  const fullscreen = useGraph(state => state.fullscreen);
  const setFormat = useFile(state => state.setFormat);
  const currentFormat = useFile(state => state.format);

  const toggleEditor = () => {
    toggleFullscreen(!fullscreen);
    gaEvent("toggle_fullscreen");
  };

  React.useEffect(() => {
    if (data?.name) window.document.title = `${data.name} | JSON Crack`;
  }, [data]);

  return (
    <StyledBottomBar>
      <StyledLeft>
        <StyledBottomBarItem onClick={toggleEditor}>
          <BiSolidDockLeft />
        </StyledBottomBarItem>
        <StyledBottomBarItem>
          {error ? (
            <Popover width="auto" shadow="md" position="top" withArrow>
              <Popover.Target>
                <Flex align="center" gap={2}>
                  <VscError color="red" />
                  <Text c="red" fw={500} fz="xs">
                    Invalid
                  </Text>
                </Flex>
              </Popover.Target>
              <Popover.Dropdown style={{ pointerEvents: "none" }}>
                <Text size="xs">{error}</Text>
              </Popover.Dropdown>
            </Popover>
          ) : (
            <Flex align="center" gap={2}>
              <VscCheck />
              <Text size="xs">Valid</Text>
            </Flex>
          )}
        </StyledBottomBarItem>
        <StyledBottomBarItem
          onClick={() => {
            toggleLiveTransform(!liveTransformEnabled);
            gaEvent("toggle_live_transform");
          }}
        >
          {liveTransformEnabled ? <VscSync /> : <VscSyncIgnored />}
          <Text fz="xs">Live Transform</Text>
        </StyledBottomBarItem>
        {!liveTransformEnabled && (
          <StyledBottomBarItem onClick={() => setContents({})} disabled={!!error}>
            <VscRunAll />
            Click to Transform
          </StyledBottomBarItem>
        )}
      </StyledLeft>

      <StyledRight>
        <Menu offset={8}>
          <Menu.Target>
            <StyledBottomBarItem>
              <Flex align="center" gap={2}>
                <MdArrowUpward />
                <Text size="xs">{currentFormat?.toUpperCase()}</Text>
              </Flex>
            </StyledBottomBarItem>
          </Menu.Target>
          <Menu.Dropdown>
            {formats.map(format => (
              <Menu.Item
                key={format.value}
                fz={12}
                onClick={() => setFormat(format.value)}
                rightSection={currentFormat === format.value && <IoMdCheckmark />}
              >
                {format.label}
              </Menu.Item>
            ))}
          </Menu.Dropdown>
        </Menu>
      </StyledRight>
    </StyledBottomBar>
  );
};


================================================
FILE: apps/www/src/features/editor/ExternalMode.tsx
================================================
import React from "react";
import { Accordion, Anchor, Code, Flex, FocusTrap, Group, Modal, Text } from "@mantine/core";

const ExternalMode = () => {
  const [isExternal, setExternal] = React.useState(false);

  React.useEffect(() => {
    if (process.env.NEXT_PUBLIC_DISABLE_EXTERNAL_MODE === "false") {
      if (typeof window !== "undefined") {
        if (window.location.pathname.includes("widget")) return setExternal(false);
        if (window.location.host !== "jsoncrack.com") return setExternal(true);
        return setExternal(false);
      }
    }
  }, []);

  if (!isExternal) return null;

  return (
    <Modal
      title="Thanks for using JSON Crack"
      opened={isExternal}
      onClose={() => setExternal(false)}
      centered
      size="lg"
    >
      <FocusTrap.InitialFocus />
      <Group>
        <Accordion variant="separated" w="100%">
          <Accordion.Item value="1">
            <Accordion.Control>How can I change the file size limit?</Accordion.Control>
            <Accordion.Panel>
              The main reason for the file size limit is to prevent performance issues, not to push
              you to upgrade. You can increase the limit by setting{" "}
              <Code>NEXT_PUBLIC_NODE_LIMIT</Code> in your <Code>.env</Code> file.
              <br />
              <br />
              If you&apos;d like to work with even larger files and unlock additional features, you
              can upgrade to the{" "}
              <Anchor
                href="https://todiagram.com?utm_source=jsoncrack&utm_medium=external-mode"
                rel="noopener"
                target="_blank"
              >
                Pro
              </Anchor>{" "}
              version.
            </Accordion.Panel>
          </Accordion.Item>
          <Accordion.Item value="2">
            <Accordion.Control>How can I stop this dialog from appearing?</Accordion.Control>
            <Accordion.Panel>
              You can disable this dialog by setting <Code>NEXT_PUBLIC_DISABLE_EXTERNAL_MODE</Code>{" "}
              to <Code>true</Code> in your <Code>.env.development</Code> file.
              <br />
              <br />
              If you want to re-enable it, simply remove or set the value to <Code>false</Code>.
            </Accordion.Panel>
          </Accordion.Item>
          <Accordion.Item value="3">
            <Accordion.Control>What are the license terms?</Accordion.Control>
            <Accordion.Panel>
              Read the full license terms on{" "}
              <Anchor
                href="https://github.com/AykutSarac/jsoncrack.com/blob/main/LICENSE.md"
                rel="noopener"
                target="_blank"
              >
                GitHub
              </Anchor>
              .
            </Accordion.Panel>
          </Accordion.Item>
          <Accordion.Item value="4">
            <Accordion.Control>How do I report a bug or request a feature?</Accordion.Control>
            <Accordion.Panel>
              You can report bugs or request features by opening an issue on our{" "}
              <Anchor
                href="https://github.com/AykutSarac/jsoncrack.com/issues"
                rel="noopener"
                target="_blank"
              >
                GitHub Issues page
              </Anchor>
              .
              <br />
              <br />
              Please provide as much detail as possible to help us address your feedback quickly.
            </Accordion.Panel>
          </Accordion.Item>
          <Accordion.Item value="5">
            <Accordion.Control>How do I contribute to the project?</Accordion.Control>
            <Accordion.Panel>
              We welcome contributions! Visit our{" "}
              <Anchor
                href="https://github.com/AykutSarac/jsoncrack.com"
                rel="noopener"
                target="_blank"
              >
                GitHub repository
              </Anchor>{" "}
              and read the{" "}
              <Anchor
                href="https://github.com/AykutSarac/jsoncrack.com/blob/main/CONTRIBUTING.md"
                rel="noopener"
                target="_blank"
              >
                contributing guide
              </Anchor>{" "}
              to get started.
            </Accordion.Panel>
          </Accordion.Item>
          <Accordion.Item value="6">
            <Accordion.Control>
              What is the difference between JSON Crack and ToDiagram?
            </Accordion.Control>
            <Accordion.Panel>
              JSON Crack is a free and open-source tool for visualizing JSON data. ToDiagram is the
              professional version that offers advanced features, higher limits, and the ability to
              edit data directly from diagrams. You can learn more or upgrade at{" "}
              <Anchor
                href="https://todiagram.com?utm_source=jsoncrack&utm_medium=external-mode"
                rel="noopener"
                target="_blank"
              >
                todiagram.com
              </Anchor>
              .
            </Accordion.Panel>
          </Accordion.Item>
        </Accordion>
      </Group>
      <Flex justify="center" align="center" gap="sm" mt="md">
        <Anchor
          href="https://github.com/AykutSarac/jsoncrack.com"
          rel="noopener"
          target="_blank"
          fz="sm"
        >
          GitHub
        </Anchor>
        <Text c="dimmed">•</Text>
        <Anchor
          href="https://todiagram.com?utm_source=jsoncrack&utm_medium=external-mode"
          rel="noopener"
          target="_blank"
          fz="sm"
        >
          ToDiagram
        </Anchor>
        <Text c="dimmed">•</Text>
        <Anchor href="https://x.com/aykutsarach" rel="noopener" target="_blank" fz="sm">
          Aykut Saraç (@aykutsarach)
        </Anchor>
      </Flex>
    </Modal>
  );
};

export default ExternalMode;


================================================
FILE: apps/www/src/features/editor/FullscreenDropzone.tsx
================================================
import React from "react";
import { Group, Text } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import toast from "react-hot-toast";
import { VscCircleSlash, VscFiles } from "react-icons/vsc";
import { FileFormat } from "../../enums/file.enum";
import useFile from "../../store/useFile";

export const FullscreenDropzone = () => {
  const setContents = useFile(state => state.setContents);

  return (
    <Dropzone.FullScreen
      maxFiles={1}
      accept={["application/json", "application/x-yaml", "text/csv", "application/xml"]}
      onReject={files => toast.error(`Unable to load file ${files[0].file.name}`)}
      onDrop={async e => {
        try {
          const fileContent = await e[0].text();
          let fileExtension = e[0].name.split(".").pop() as FileFormat | undefined;
          if (!fileExtension) fileExtension = FileFormat.JSON;
          setContents({ contents: fileContent, format: fileExtension, hasChanges: false });
        } catch (err) {
          toast.error("An error occurred while reading the file.");
          console.error(err);
        }
      }}
    >
      <Group
        justify="center"
        ta="center"
        align="center"
        gap="xl"
        h="100vh"
        style={{ pointerEvents: "none" }}
      >
        <Dropzone.Accept>
          <VscFiles size={100} />
          <Text fz="h1" fw={500} mt="lg">
            Upload to JSON Crack
          </Text>
          <Text fz="lg" c="dimmed" mt="sm">
            (Max file size: 300 KB)
          </Text>
        </Dropzone.Accept>
        <Dropzone.Reject>
          <VscCircleSlash size={100} />
          <Text fz="h1" fw={500} mt="lg">
            Invalid file
          </Text>
          <Text fz="lg" c="dimmed" mt="sm">
            Allowed formats are JSON, YAML, CSV, XML
          </Text>
        </Dropzone.Reject>
      </Group>
    </Dropzone.FullScreen>
  );
};


================================================
FILE: apps/www/src/features/editor/LiveEditor.tsx
================================================
import React from "react";
import { useSessionStorage } from "@mantine/hooks";
import styled from "styled-components";
import { ViewMode } from "../../enums/viewMode.enum";
import { GraphView } from "./views/GraphView";
import { TreeView } from "./views/TreeView";

const StyledLiveEditor = styled.div`
  position: relative;
  height: 100%;
  background: ${({ theme }) => theme.GRID_BG_COLOR};
  overflow: auto;

  & > ul {
    margin-top: 0 !important;
    padding: 12px !important;
    font-family: monospace;
    font-size: 14px;
    font-weight: 500;
  }

  .tab-group {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 2;
  }
`;

const View = () => {
  const [viewMode] = useSessionStorage({
    key: "viewMode",
    defaultValue: ViewMode.Graph,
  });

  if (viewMode === ViewMode.Graph) return <GraphView />;
  if (viewMode === ViewMode.Tree) return <TreeView />;
  return null;
};

const LiveEditor = () => {
  return (
    <StyledLiveEditor onContextMenuCapture={e => e.preventDefault()}>
      <View />
    </StyledLiveEditor>
  );
};

export default LiveEditor;


================================================
FILE: apps/www/src/features/editor/TextEditor.tsx
================================================
import React, { useCallback } from "react";
import { LoadingOverlay } from "@mantine/core";
import styled from "styled-components";
import Editor, { type EditorProps, loader, type OnMount, useMonaco } from "@monaco-editor/react";
import useConfig from "../../store/useConfig";
import useFile from "../../store/useFile";

loader.config({
  paths: {
    vs: "https://unpkg.com/monaco-editor@0.55.1/min/vs",
  },
});

const editorOptions: EditorProps["options"] = {
  tabSize: 2,
  formatOnType: true,
  minimap: { enabled: false },
  stickyScroll: { enabled: false },
  scrollBeyondLastLine: false,
  placeholder: "Start typing...",
};

const TextEditor = () => {
  const monaco = useMonaco();
  const contents = useFile(state => state.contents);
  const setContents = useFile(state => state.setContents);
  const setError = useFile(state => state.setError);
  const jsonSchema = useFile(state => state.jsonSchema);
  const getHasChanges = useFile(state => state.getHasChanges);
  const theme = useConfig(state => (state.darkmodeEnabled ? "vs-dark" : "light"));
  const fileType = useFile(state => state.format);
  const jsonDefaults = (monaco?.languages as any)?.json?.jsonDefaults as
    | { setDiagnosticsOptions: (options: unknown) => void }
    | undefined;

  React.useEffect(() => {
    if (!jsonDefaults) return;

    jsonDefaults.setDiagnosticsOptions({
      validate: true,
      allowComments: true,
      enableSchemaRequest: true,
      ...(jsonSchema && {
        schemas: [
          {
            uri: "http://myserver/foo-schema.json",
            fileMatch: ["*"],
            schema: jsonSchema,
          },
        ],
      }),
    });
  }, [jsonDefaults, jsonSchema]);

  React.useEffect(() => {
    const beforeunload = (e: BeforeUnloadEvent) => {
      if (getHasChanges()) {
        const confirmationMessage =
          "Unsaved changes, if you leave before saving  your changes will be lost";

        (e || window.event).returnValue = confirmationMessage; //Gecko + IE
        return confirmationMessage;
      }
    };

    window.addEventListener("beforeunload", beforeunload);

    return () => {
      window.removeEventListener("beforeunload", beforeunload);
    };
  }, [getHasChanges]);

  const handleMount: OnMount = useCallback(editor => {
    editor.onDidPaste(() => {
      editor.getAction("editor.action.formatDocument")?.run();
    });
  }, []);

  return (
    <StyledEditorWrapper>
      <StyledWrapper>
        <Editor
          height="100%"
          language={fileType}
          theme={theme}
          value={contents}
          options={editorOptions}
          onMount={handleMount}
          onValidate={errors => setError(errors[0]?.message || "")}
          onChange={contents => setContents({ contents, skipUpdate: true })}
          loading={<LoadingOverlay visible />}
        />
      </StyledWrapper>
    </StyledEditorWrapper>
  );
};

export default TextEditor;

const StyledEditorWrapper = styled.div`
  display: flex;
  flex-direction: column;
  height: 100%;
  user-select: none;
`;

const StyledWrapper = styled.div`
  display: grid;
  height: 100%;
  grid-template-columns: 100%;
  grid-template-rows: minmax(0, 1fr);
`;


================================================
FILE: apps/www/src/features/editor/Toolbar/FileMenu.tsx
================================================
import React from "react";
import { Flex, Menu } from "@mantine/core";
import { event as gaEvent } from "nextjs-google-analytics";
import { CgChevronDown } from "react-icons/cg";
import useFile from "../../../store/useFile";
import { useModal } from "../../../store/useModal";
import { StyledToolElement } from "./styles";

export const FileMenu = () => {
  const setVisible = useModal(state => state.setVisible);
  const getContents = useFile(state => state.getContents);
  const getFormat = useFile(state => state.getFormat);

  const handleSave = () => {
    const a = document.createElement("a");
    const file = new Blob([getContents()], { type: "text/plain" });

    a.href = window.URL.createObjectURL(file);
    a.download = `jsoncrack.${getFormat()}`;
    a.click();

    gaEvent("save_file", { label: getFormat() });
  };

  return (
    <Menu shadow="md" withArrow>
      <Menu.Target>
        <StyledToolElement title="File">
          <Flex align="center" gap={3}>
            File
            <CgChevronDown />
          </Flex>
        </StyledToolElement>
      </Menu.Target>
      <Menu.Dropdown>
        <Menu.Item onClick={() => setVisible("ImportModal", true)}>Import</Menu.Item>
        <Menu.Item onClick={handleSave}>Export</Menu.Item>
      </Menu.Dropdown>
    </Menu>
  );
};


================================================
FILE: apps/www/src/features/editor/Toolbar/SearchInput.tsx
================================================
import React from "react";
import { Flex, Text, TextInput } from "@mantine/core";
import { getHotkeyHandler } from "@mantine/hooks";
import { useOs } from "@mantine/hooks";
import { AiOutlineSearch } from "react-icons/ai";
import { useFocusNode } from "../../../hooks/useFocusNode";

export const SearchInput = () => {
  const [searchValue, setValue, skip, nodeCount, currentNode] = useFocusNode();
  const os = useOs();

  const coreKey = os === "macos" ? "⌘" : "Ctrl";

  return (
    <TextInput
      variant="unstyled"
      type="search"
      size="xs"
      id="search-node"
      w={180}
      value={searchValue}
      onChange={e => setValue(e.currentTarget.value)}
      placeholder={`Search Node (${coreKey} + F)`}
      autoComplete="off"
      autoCorrect="off"
      onKeyDown={getHotkeyHandler([["Enter", skip]])}
      leftSection={<AiOutlineSearch />}
      rightSection={
        searchValue && (
          <Flex h={30} align="center">
            <Text size="xs" c="dimmed" pr="md">
              {searchValue && `${nodeCount}/${nodeCount > 0 ? currentNode + 1 : "0"}`}
            </Text>
          </Flex>
        )
      }
      style={{ borderBottom: "1px solid gray" }}
    />
  );
};


================================================
FILE: apps/www/src/features/editor/Toolbar/ThemeToggle.tsx
================================================
import { FaMoon, FaSun } from "react-icons/fa6";
import useConfig from "../../../store/useConfig";
import { StyledToolElement } from "./styles";

export const ThemeToggle = () => {
  const darkmodeEnabled = useConfig(state => state.darkmodeEnabled);
  const toggleDarkMode = useConfig(state => state.toggleDarkMode);

  return (
    <StyledToolElement title="Fullscreen" onClick={() => toggleDarkMode(!darkmodeEnabled)}>
      {!darkmodeEnabled ? <FaMoon size="18" /> : <FaSun size="18" />}
    </StyledToolElement>
  );
};


================================================
FILE: apps/www/src/features/editor/Toolbar/ToolsMenu.tsx
================================================
import React from "react";
import { Menu, Flex } from "@mantine/core";
import { event as gaEvent } from "nextjs-google-analytics";
import { CgChevronDown } from "react-icons/cg";
import { MdFilterListAlt } from "react-icons/md";
import { VscSearchFuzzy, VscJson, VscGroupByRefType } from "react-icons/vsc";
import { useModal } from "../../../store/useModal";
import { StyledToolElement } from "./styles";

export const ToolsMenu = () => {
  const setVisible = useModal(state => state.setVisible);

  return (
    <Menu shadow="md" withArrow>
      <Menu.Target>
        <StyledToolElement onClick={() => gaEvent("show_tools_menu")}>
          <Flex align="center" gap={3}>
            Tools <CgChevronDown />
          </Flex>
        </StyledToolElement>
      </Menu.Target>
      <Menu.Dropdown>
        <Menu.Item
          leftSection={<VscSearchFuzzy />}
          onClick={() => {
            setVisible("JQModal", true);
            gaEvent("open_jq_modal");
          }}
        >
          JSON Query (jq)
        </Menu.Item>
        <Menu.Item
          leftSection={<MdFilterListAlt />}
          onClick={() => {
            setVisible("JPathModal", true);
            gaEvent("open_json_path_modal");
          }}
        >
          JSON Path
        </Menu.Item>
        <Menu.Item
          leftSection={<VscJson />}
          onClick={() => {
            setVisible("SchemaModal", true);
            gaEvent("open_schema_modal");
          }}
        >
          JSON Schema
        </Menu.Item>
        <Menu.Divider />
        <Menu.Item
          leftSection={<VscGroupByRefType />}
          onClick={() => {
            setVisible("TypeModal", true);
            gaEvent("open_type_modal");
          }}
        >
          Generate Type
        </Menu.Item>
      </Menu.Dropdown>
    </Menu>
  );
};


================================================
FILE: apps/www/src/features/editor/Toolbar/ViewMenu.tsx
================================================
import { Menu, Flex, SegmentedControl } from "@mantine/core";
import { useSessionStorage } from "@mantine/hooks";
import { event as gaEvent } from "nextjs-google-analytics";
import { CgChevronDown } from "react-icons/cg";
import { ViewMode } from "../../../enums/viewMode.enum";
import { StyledToolElement } from "./styles";

export const ViewMenu = () => {
  const [viewMode, setViewMode] = useSessionStorage({
    key: "viewMode",
    defaultValue: ViewMode.Graph,
  });

  return (
    <Menu shadow="md" closeOnItemClick={false} withArrow>
      <Menu.Target>
        <StyledToolElement onClick={() => gaEvent("show_view_menu")}>
          <Flex align="center" gap={3}>
            View <CgChevronDown />
          </Flex>
        </StyledToolElement>
      </Menu.Target>
      <Menu.Dropdown>
        <SegmentedControl
          size="md"
          w="100%"
          value={viewMode}
          onChange={e => {
            setViewMode(e as ViewMode);
            gaEvent("change_view_mode", { label: e });
          }}
          data={[
            { value: ViewMode.Graph, label: "Graph" },
            { value: ViewMode.Tree, label: "Tree" },
          ]}
          fullWidth
          orientation="vertical"
        />
      </Menu.Dropdown>
    </Menu>
  );
};


================================================
FILE: apps/www/src/features/editor/Toolbar/index.tsx
================================================
import React from "react";
import Link from "next/link";
import { Flex, Group } from "@mantine/core";
import styled from "styled-components";
import toast from "react-hot-toast";
import { AiOutlineFullscreen } from "react-icons/ai";
import { FaGithub } from "react-icons/fa6";
import { JSONCrackLogo } from "../../../layout/JSONCrackBrandLogo";
import { FileMenu } from "./FileMenu";
import { ThemeToggle } from "./ThemeToggle";
import { ToolsMenu } from "./ToolsMenu";
import { ViewMenu } from "./ViewMenu";
import { StyledToolElement } from "./styles";

const StyledTools = styled.div`
  position: relative;
  display: flex;
  width: 100%;
  align-items: center;
  gap: 4px;
  justify-content: space-between;
  height: 45px;
  padding: 6px 12px;
  background: ${({ theme }) => theme.TOOLBAR_BG};
  color: ${({ theme }) => theme.SILVER};
  z-index: 36;
  border-bottom: 1px solid ${({ theme }) => theme.SILVER_DARK};

  @media only screen and (max-width: 320px) {
    display: none;
  }
`;

function fullscreenBrowser() {
  if (!document.fullscreenElement) {
    document.documentElement.requestFullscreen().catch(() => {
      toast.error("Unable to enter fullscreen mode.");
    });
  } else if (document.exitFullscreen) {
    document.exitFullscreen();
  }
}

export const Toolbar = () => {
  return (
    <StyledTools>
      <Group gap="xs" justify="left" w="100%" style={{ flexWrap: "nowrap" }}>
        <StyledToolElement title="JSON Crack">
          <Flex gap="xs" align="center" justify="center">
            <JSONCrackLogo fontSize="14px" hideLogo />
          </Flex>
        </StyledToolElement>
        <FileMenu />
        <ViewMenu />
        <ToolsMenu />
      </Group>
      <Group gap="xs" justify="right" w="100%" style={{ flexWrap: "nowrap" }}>
        <ThemeToggle />
        <Link href="https://github.com/AykutSarac/jsoncrack.com" rel="noopener" target="_blank">
          <StyledToolElement title="GitHub">
            <FaGithub size="20" />
          </StyledToolElement>
        </Link>
        <StyledToolElement title="Fullscreen" onClick={fullscreenBrowser}>
          <AiOutlineFullscreen size="20" />
        </StyledToolElement>
      </Group>
    </StyledTools>
  );
};


================================================
FILE: apps/www/src/features/editor/Toolbar/styles.ts
================================================
import styled from "styled-components";

export const StyledToolElement = styled.button<{ $hide?: boolean; $highlight?: boolean }>`
  display: ${({ $hide }) => ($hide ? "none" : "flex")};
  align-items: center;
  gap: 4px;
  place-content: center;
  font-size: 14px;
  background: ${({ $highlight }) =>
    $highlight ? "linear-gradient(rgba(0, 0, 0, 0.1) 0 0)" : "none"};
  color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
  padding: 6px;
  border-radius: 3px;
  white-space: nowrap;

  &:hover {
    background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);
  }

  &:hover {
    color: ${({ theme }) => theme.INTERACTIVE_HOVER};
    opacity: 1;
    box-shadow: none;
  }
`;


================================================
FILE: apps/www/src/features/editor/views/GraphView/CustomEdge/index.tsx
================================================
import React from "react";
import { useComputedColorScheme } from "@mantine/core";
import type { EdgeProps } from "reaflow";
import { Edge } from "reaflow";
import useGraph from "../stores/useGraph";

const CustomEdgeWrapper = (props: EdgeProps) => {
  const colorScheme = useComputedColorScheme();
  const viewPort = useGraph(state => state.viewPort);
  const [hovered, setHovered] = React.useState(false);

  const handeClick = () => {
    const targetNodeId = (props.properties as { to?: string } | undefined)?.to;
    const targetNodeDom = document.querySelector(
      `[data-id$="node-${targetNodeId}"]`
    ) as HTMLElement;
    if (targetNodeDom && targetNodeDom.parentElement) {
      viewPort?.camera.centerFitElementIntoView(targetNodeDom.parentElement, {
        elementExtraMarginForZoom: 150,
      });
    }
  };

  return (
    <Edge
      containerClassName={`edge-${props.id}`}
      onClick={handeClick}
      onEnter={() => setHovered(true)}
      onLeave={() => setHovered(false)}
      style={{
        stroke: colorScheme === "dark" ? "#444444" : "#BCBEC0",
        ...(hovered && { stroke: "#3B82F6" }),
        strokeWidth: 1.5,
      }}
      {...props}
    />
  );
};

export const CustomEdge = React.memo(CustomEdgeWrapper);


================================================
FILE: apps/www/src/features/editor/views/GraphView/CustomNode/ObjectNode.tsx
================================================
import React from "react";
import type { NodeData } from "jsoncrack-react";
import type { CustomNodeProps } from ".";
import { NODE_DIMENSIONS } from "../../../../../constants/graph";
import { TextRenderer } from "./TextRenderer";
import * as Styled from "./styles";

type RowProps = {
  row: NodeData["text"][number];
  x: number;
  y: number;
  index: number;
};

const Row = ({ row, x, y, index }: RowProps) => {
  const rowPosition = index * NODE_DIMENSIONS.ROW_HEIGHT;

  const getRowText = () => {
    if (row.type === "object") return `{${row.childrenCount ?? 0} keys}`;
    if (row.type === "array") return `[${row.childrenCount ?? 0} items]`;
    return row.value;
  };

  return (
    <Styled.StyledRow
      $value={row.value}
      data-key={`${row.key}: ${row.value}`}
      data-x={x}
      data-y={y + rowPosition}
    >
      <Styled.StyledKey $type="object">{row.key}: </Styled.StyledKey>
      <TextRenderer>{getRowText()}</TextRenderer>
    </Styled.StyledRow>
  );
};

const Node = ({ node, x, y }: CustomNodeProps) => (
  <Styled.StyledForeignObject
    data-id={`node-${node.id}`}
    width={node.width}
    height={node.height}
    x={0}
    y={0}
    $isObject
  >
    {node.text.map((row, index) => (
      <Row key={`${node.id}-${index}`} row={row} x={x} y={y} index={index} />
    ))}
  </Styled.StyledForeignObject>
);

function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {
  return (
    JSON.stringify(prev.node.text) === JSON.stringify(next.node.text) &&
    prev.node.width === next.node.width
  );
}

export const ObjectNode = React.memo(Node, propsAreEqual);


================================================
FILE: apps/www/src/features/editor/views/GraphView/CustomNode/TextNode.tsx
================================================
import React from "react";
import styled from "styled-components";
import type { CustomNodeProps } from ".";
import { TextRenderer } from "./TextRenderer";
import * as Styled from "./styles";

const StyledTextNodeWrapper = styled.span<{ $isParent: boolean }>`
  display: flex;
  justify-content: ${({ $isParent }) => ($isParent ? "center" : "flex-start")};
  align-items: center;
  height: 100%;
  width: 100%;
  overflow: hidden;
  padding: 0 10px;
`;

const Node = ({ node, x, y }: CustomNodeProps) => {
  const { text, width, height } = node;
  const value = text[0].value;

  return (
    <Styled.StyledForeignObject
      data-id={`node-${node.id}`}
      width={width}
      height={height}
      x={0}
      y={0}
    >
      <StyledTextNodeWrapper
        data-x={x}
        data-y={y}
        data-key={JSON.stringify(text)}
        $isParent={false}
      >
        <Styled.StyledKey $value={value} $type={typeof text[0].value}>
          <TextRenderer>{value}</TextRenderer>
        </Styled.StyledKey>
      </StyledTextNodeWrapper>
    </Styled.StyledForeignObject>
  );
};

function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {
  return prev.node.text === next.node.text && prev.node.width === next.node.width;
}

export const TextNode = React.memo(Node, propsAreEqual);


================================================
FILE: apps/www/src/features/editor/views/GraphView/CustomNode/TextRenderer.tsx
================================================
import React from "react";
import { ColorSwatch } from "@mantine/core";
import styled from "styled-components";

const StyledRow = styled.span`
  display: inline-flex;
  align-items: center;
  overflow: hidden;
  gap: 4px;
  vertical-align: middle;
`;

const isURL = (word: string) => {
  const urlPattern =
    /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/gm;

  return word?.match(urlPattern);
};

const Linkify = (text: string) => {
  const addMarkup = (word: string) => {
    return isURL(word)
      ? `<a onclick="event.stopPropagation()" href="${word}" style="text-decoration: underline; pointer-events: all;" target="_blank" rel="noopener noreferrer">${word}</a>`
      : word;
  };

  const words = text.split(" ");
  const formatedWords = words.map(w => addMarkup(w));
  const html = formatedWords.join(" ");
  return <span dangerouslySetInnerHTML={{ __html: html }} />;
};

export const TextRenderer = ({ children }: React.PropsWithChildren) => {
  if (typeof children === "string" && isURL(children)) return Linkify(children);

  if (typeof children === "string" && isColorFormat(children)) {
    return (
      <StyledRow>
        <ColorSwatch size={12} radius={4} mr={4} color={children} />
        {children}
      </StyledRow>
    );
  }

  return <>{`${children}`}</>;
};

function isColorFormat(colorString: string) {
  const hexCodeRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
  const rgbRegex = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/;
  const rgbaRegex = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(0|1|0\.\d+)\s*\)$/;

  return (
    hexCodeRegex.test(colorString) || rgbRegex.test(colorString) || rgbaRegex.test(colorString)
  );
}


================================================
FILE: apps/www/src/features/editor/views/GraphView/CustomNode/index.tsx
================================================
import React from "react";
import { useComputedColorScheme } from "@mantine/core";
import type { NodeData } from "jsoncrack-react";
import type { NodeProps } from "reaflow";
import { Node } from "reaflow";
import { useModal } from "../../../../../store/useModal";
import useGraph from "../stores/useGraph";
import { ObjectNode } from "./ObjectNode";
import { TextNode } from "./TextNode";

export interface CustomNodeProps {
  node: NodeData;
  x: number;
  y: number;
  hasCollapse?: boolean;
}

const CustomNodeWrapper = (nodeProps: NodeProps<NodeData>) => {
  const setSelectedNode = useGraph(state => state.setSelectedNode);
  const setVisible = useModal(state => state.setVisible);
  const colorScheme = useComputedColorScheme();

  const handleNodeClick = React.useCallback(
    (_: React.MouseEvent<SVGGElement, MouseEvent>, data: NodeData) => {
      if (setSelectedNode) setSelectedNode(data);
      setVisible("NodeModal", true);
    },
    [setSelectedNode, setVisible]
  );

  return (
    <Node
      {...nodeProps}
      onClick={handleNodeClick as any}
      animated={false}
      label={null as any}
      onEnter={ev => {
        ev.currentTarget.style.stroke = "#3B82F6";
      }}
      onLeave={ev => {
        ev.currentTarget.style.stroke = colorScheme === "dark" ? "#424242" : "#BCBEC0";
      }}
      style={{
        fill: colorScheme === "dark" ? "#292929" : "#ffffff",
        stroke: colorScheme === "dark" ? "#424242" : "#BCBEC0",
        strokeWidth: 1,
      }}
    >
      {({ node, x, y }) => {
        const hasKey = nodeProps.properties.text[0].key;
        if (!hasKey) return <TextNode node={nodeProps.properties as NodeData} x={x} y={y} />;

        return <ObjectNode node={node as NodeData} x={x} y={y} />;
      }}
    </Node>
  );
};

export const CustomNode = React.memo(CustomNodeWrapper);


================================================
FILE: apps/www/src/features/editor/views/GraphView/CustomNode/styles.tsx
================================================
import type { DefaultTheme } from "styled-components";
import styled from "styled-components";
import { LinkItUrl } from "react-linkify-it";
import { NODE_DIMENSIONS } from "../../../../../constants/graph";

type TextColorFn = {
  theme: DefaultTheme;
  $type?: string;
  $value?: string | number | null | boolean;
};

function getTextColor({ $value, $type, theme }: TextColorFn) {
  if ($value === null) return theme.NODE_COLORS.NULL;
  if ($type === "object") return theme.NODE_COLORS.NODE_KEY;
  if ($type === "number") return theme.NODE_COLORS.INTEGER;
  if ($value === true) return theme.NODE_COLORS.BOOL.TRUE;
  if ($value === false) return theme.NODE_COLORS.BOOL.FALSE;
  return theme.NODE_COLORS.NODE_VALUE;
}

export const StyledLinkItUrl = styled(LinkItUrl)`
  text-decoration: underline;
  pointer-events: all;
`;

export const StyledForeignObject = styled.foreignObject<{ $isObject?: boolean }>`
  text-align: ${({ $isObject }) => !$isObject && "center"};
  color: ${({ theme }) => theme.NODE_COLORS.TEXT};
  font-family: monospace;
  font-size: 12px;
  font-weight: 500;
  overflow: hidden;
  pointer-events: none;

  &.searched {
    background: rgba(27, 255, 0, 0.1);
    border: 1px solid ${({ theme }) => theme.TEXT_POSITIVE};
    border-radius: 2px;
    box-sizing: border-box;
  }

  .highlight {
    background: rgba(255, 214, 0, 0.15);
  }

  .renderVisible {
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 12px;
    width: 100%;
    height: 100%;
    overflow: hidden;
    cursor: pointer;
  }
`;

export const StyledKey = styled.span<{
  $type: TextColorFn["$type"];
  $value?: TextColorFn["$value"];
}>`
  display: inline;
  align-items: center;
  justify-content: center;
  flex: 1;
  min-width: 0;
  height: auto;
  line-height: inherit;
  padding: 0; // Remove padding
  color: ${({ theme, $type, $value = "" }) => getTextColor({ $value, $type, theme })};
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
`;

export const StyledRow = styled.span<{ $value: TextColorFn["$value"] }>`
  padding: 3px 10px;
  height: ${NODE_DIMENSIONS.ROW_HEIGHT}px;
  line-height: 24px;
  color: ${({ theme, $value }) => getTextColor({ $value, theme, $type: typeof $value })};
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border-bottom: 1px solid ${({ theme }) => theme.NODE_COLORS.DIVIDER};
  box-sizing: border-box;

  &:last-of-type {
    border-bottom: none;
  }

  .searched & {
    border-bottom: 1px solid ${({ theme }) => theme.TEXT_POSITIVE};
  }
`;

export const StyledChildrenCount = styled.span`
  color: ${({ theme }) => theme.NODE_COLORS.CHILD_COUNT};
  padding: 10px;
  margin-left: -15px;
`;


================================================
FILE: apps/www/src/features/editor/views/GraphView/NotSupported.tsx
================================================
import React from "react";
import { Anchor, Button, Image, Overlay, Stack, Text } from "@mantine/core";
import styled, { keyframes } from "styled-components";
import useConfig from "../../../../store/useConfig";

const shineEffect = keyframes`
  0% {
    transform: translateX(-120%) rotate(25deg);
    opacity: 0.5;
  }
  5% {
    opacity: 0.5;
    transform: translateX(-80%) rotate(25deg);
  }
  70% {
    transform: translateX(80%) rotate(25deg);
    opacity: 0.5;
  }
  80% {
    transform: translateX(120%) rotate(25deg);
    opacity: 0;
  }
  100% {
    transform: translateX(120%) rotate(25deg);
    opacity: 0;
  }
`;

const ShiningButton = styled.div`
  position: relative;
  overflow: hidden;
  display: inline-block;
  border-radius: 0.5rem;
  z-index: 10;

  &::before {
    content: "";
    position: absolute;
    top: -50%;
    left: -50%;
    width: 200%;
    height: 200%;
    background: linear-gradient(
      to right,
      rgba(255, 255, 255, 0) 0%,
      rgba(255, 255, 255, 0) 35%,
      rgba(255, 255, 255, 0.5) 50%,
      rgba(255, 255, 255, 0) 65%,
      rgba(255, 255, 255, 0) 100%
    );
    transform: translateX(-120%) rotate(25deg);
    z-index: 20;
    pointer-events: none;
    animation: ${shineEffect} 4s ease-out infinite;
    transition: transform 0.2s ease-out;
  }
`;

export const NotSupported = () => {
  const darkmodeEnabled = useConfig(state => state.darkmodeEnabled);

  return (
    <Overlay
      backgroundOpacity={0.8}
      color={darkmodeEnabled ? "gray" : "rgb(226, 240, 243)"}
      blur="1.5"
      center
    >
      <Stack maw="60%" align="center" justify="center" gap="sm">
        <Image src="https://todiagram.com/logo.svg" alt="Unsupported" w={72} h={72} />
        <Text fz="48" fw={600} c="bright">
          Time to upgrade!
        </Text>
        <Text ta="center" size="lg" fw={500} c="gray" maw="600">
          This diagram is too large and not supported at JSON Crack.
          <br />
          Try{" "}
          <Anchor
            inherit
            c="teal"
            fw="500"
            href="https://todiagram.com/editor?utm_source=jsoncrack&utm_medium=data_limit"
            target="_blank"
            rel="noopener"
          >
            ToDiagram
          </Anchor>{" "}
          for larger diagrams and more features.
        </Text>
        <ShiningButton style={{ marginTop: "16px", position: "relative" }}>
          <Button
            component="a"
            href="https://todiagram.com/editor?utm_source=jsoncrack&utm_medium=data_limit"
            rel="noopener"
            size="lg"
            w="200"
            target="_blank"
            color="teal"
          >
            Try now &rarr;
          </Button>
        </ShiningButton>
      </Stack>
    </Overlay>
  );
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/OptionsMenu.tsx
================================================
import React from "react";
import { ActionIcon, Flex, Menu, Text } from "@mantine/core";
import { useHotkeys } from "@mantine/hooks";
import styled from "styled-components";
import type { LayoutDirection } from "jsoncrack-react";
import { event as gaEvent } from "nextjs-google-analytics";
import { BsCheck2 } from "react-icons/bs";
import { LuImageDown, LuMenu } from "react-icons/lu";
import { TiFlowMerge } from "react-icons/ti";
import useConfig from "../../../../store/useConfig";
import { useModal } from "../../../../store/useModal";
import useGraph from "./stores/useGraph";

const StyledFlowIcon = styled(TiFlowMerge)<{ rotate: number }>`
  transform: rotate(${({ rotate }) => `${rotate}deg`});
`;

const getNextDirection = (direction: LayoutDirection) => {
  if (direction === "RIGHT") return "DOWN";
  if (direction === "DOWN") return "LEFT";
  if (direction === "LEFT") return "UP";
  return "RIGHT";
};

const rotateLayout = (direction: LayoutDirection) => {
  if (direction === "LEFT") return 90;
  if (direction === "UP") return 180;
  if (direction === "RIGHT") return 270;
  return 360;
};

export const OptionsMenu = () => {
  const toggleGestures = useConfig(state => state.toggleGestures);
  const toggleRulers = useConfig(state => state.toggleRulers);
  const gesturesEnabled = useConfig(state => state.gesturesEnabled);
  const rulersEnabled = useConfig(state => state.rulersEnabled);
  const setDirection = useGraph(state => state.setDirection);
  const direction = useGraph(state => state.direction);
  const setVisible = useModal(state => state.setVisible);
  const [coreKey, setCoreKey] = React.useState("CTRL");

  const toggleDirection = () => {
    const nextDirection = getNextDirection(direction || "RIGHT");
    if (setDirection) setDirection(nextDirection);
  };

  useHotkeys(
    [
      ["mod+shift+d", toggleDirection],
      [
        "mod+f",
        () => {
          const input = document.querySelector("#search-node") as HTMLInputElement;
          input.focus();
        },
      ],
    ],
    []
  );

  React.useEffect(() => {
    if (typeof window !== "undefined") {
      setCoreKey(navigator.userAgent.indexOf("Mac OS X") ? "⌘" : "CTRL");
    }
  }, []);

  return (
    <Flex
      gap="xs"
      align="center"
      style={{
        position: "absolute",
        top: "10px",
        left: "10px",
        zIndex: 100,
      }}
    >
      <Menu withArrow>
        <Menu.Target>
          <ActionIcon aria-label="actions" size="lg" color="gray" variant="light">
            <LuMenu size="18" />
          </ActionIcon>
        </Menu.Target>
        <Menu.Dropdown>
          <Menu.Item
            fz="sm"
            leftSection={<LuImageDown color="gray" />}
            onClick={() => setVisible("DownloadModal", true)}
          >
            <Flex justify="space-between" gap="md">
              <Text inherit>Export</Text>
              <Text fz="xs" ml="md" c="dimmed">
                {coreKey} + S
              </Text>
            </Flex>
          </Menu.Item>
          <Menu.Item
            fz="sm"
            onClick={() => {
              toggleDirection();
              gaEvent("rotate_layout", { label: direction });
            }}
            leftSection={
              <StyledFlowIcon color="gray" rotate={rotateLayout(direction || "RIGHT")} />
            }
            rightSection={
              <Text fz="xs" ml="md" c="dimmed">
                {coreKey} Shift D
              </Text>
            }
            closeMenuOnClick={false}
          >
            Rotate Layout
          </Menu.Item>
          <Menu.Divider />
          <Menu.Sub position="right" offset={0}>
            <Menu.Sub.Target>
              <Menu.Sub.Item fz="sm">View Options</Menu.Sub.Item>
            </Menu.Sub.Target>
            <Menu.Sub.Dropdown>
              <Menu.Item
                fz="sm"
                leftSection={<BsCheck2 opacity={rulersEnabled ? 100 : 0} />}
                onClick={() => {
                  toggleRulers(!rulersEnabled);
                  gaEvent("toggle_rulers", { label: rulersEnabled ? "on" : "off" });
                }}
                closeMenuOnClick={false}
              >
                Rulers
              </Menu.Item>
              <Menu.Item
                fz="sm"
                leftSection={<BsCheck2 opacity={gesturesEnabled ? 100 : 0} />}
                onClick={() => {
                  toggleGestures(!gesturesEnabled);
                  gaEvent("toggle_gestures", { label: gesturesEnabled ? "on" : "off" });
                }}
              >
                Zoom on Scroll
              </Menu.Item>
            </Menu.Sub.Dropdown>
          </Menu.Sub>
        </Menu.Dropdown>
      </Menu>
    </Flex>
  );
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/SecureInfo.tsx
================================================
import React from "react";
import { ThemeIcon, Tooltip } from "@mantine/core";
import { LuShieldCheck } from "react-icons/lu";

export const SecureInfo = () => {
  return (
    <Tooltip
      label="Your data is processed locally on your device."
      fz="xs"
      ta="center"
      maw="200"
      multiline
      withArrow
    >
      <ThemeIcon
        variant="light"
        color="teal"
        size="36"
        style={{
          position: "absolute",
          bottom: "10px",
          right: "10px",
          zIndex: 100,
        }}
        radius="xl"
      >
        <LuShieldCheck size="22" />
      </ThemeIcon>
    </Tooltip>
  );
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/ZoomControl.tsx
================================================
import React from "react";
import { ActionIcon, Flex, Tooltip, Text } from "@mantine/core";
import { useHotkeys } from "@mantine/hooks";
import { event as gaEvent } from "nextjs-google-analytics";
import { LuFocus, LuMaximize, LuMinus, LuPlus } from "react-icons/lu";
import { SearchInput } from "../../Toolbar/SearchInput";
import useGraph from "./stores/useGraph";

export const ZoomControl = () => {
  const zoomIn = useGraph(state => state.zoomIn);
  const zoomOut = useGraph(state => state.zoomOut);
  const centerView = useGraph(state => state.centerView);
  const focusFirstNode = useGraph(state => state.focusFirstNode);

  useHotkeys(
    [
      ["mod+[plus]", zoomIn, { usePhysicalKeys: true }],
      ["mod+[minus]", zoomOut, { usePhysicalKeys: true }],
      ["shift+Digit1", focusFirstNode, { usePhysicalKeys: true }],
      ["shift+Digit2", centerView, { usePhysicalKeys: true }],
    ],
    []
  );

  return (
    <Flex
      align="center"
      gap="xs"
      style={{
        position: "absolute",
        bottom: "10px",
        left: "10px",
        alignItems: "start",
        zIndex: 100,
      }}
    >
      <ActionIcon.Group borderWidth={0}>
        <Tooltip
          label={
            <Flex fz="xs" gap="md">
              <Text fz="xs">Center first item</Text>
              <Text fz="xs" c="dimmed">
                ⇧ 1
              </Text>
            </Flex>
          }
          withArrow
        >
          <ActionIcon
            size="lg"
            variant="light"
            color="gray"
            onClick={() => {
              focusFirstNode();
              gaEvent("focus_first_node");
            }}
          >
            <LuFocus />
          </ActionIcon>
        </Tooltip>
        <Tooltip
          label={
            <Flex fz="xs" gap="md">
              <Text fz="xs">Fit to center</Text>
              <Text fz="xs" c="dimmed">
                ⇧ 2
              </Text>
            </Flex>
          }
          withArrow
        >
          <ActionIcon
            size="lg"
            variant="light"
            color="gray"
            onClick={() => {
              centerView();
              gaEvent("center_view");
            }}
          >
            <LuMaximize />
          </ActionIcon>
        </Tooltip>
        <ActionIcon
          size="lg"
          variant="light"
          color="gray"
          onClick={() => {
            zoomOut();
            gaEvent("zoom_out");
          }}
        >
          <LuMinus />
        </ActionIcon>
        <ActionIcon
          size="lg"
          variant="light"
          color="gray"
          onClick={() => {
            zoomIn();
            gaEvent("zoom_in");
          }}
        >
          <LuPlus />
        </ActionIcon>
      </ActionIcon.Group>
      <SearchInput />
    </Flex>
  );
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/index.tsx
================================================
import React from "react";
import { Box } from "@mantine/core";
import styled from "styled-components";
import { JSONCrack } from "jsoncrack-react";
import type { NodeData } from "jsoncrack-react";
import { useLongPress } from "use-long-press";
import { SUPPORTED_LIMIT } from "../../../../constants/graph";
import useConfig from "../../../../store/useConfig";
import useJson from "../../../../store/useJson";
import { useModal } from "../../../../store/useModal";
import { NotSupported } from "./NotSupported";
import { OptionsMenu } from "./OptionsMenu";
import { SecureInfo } from "./SecureInfo";
import { ZoomControl } from "./ZoomControl";
import useGraph from "./stores/useGraph";

const StyledEditorWrapper = styled.div<{ $widget: boolean }>`
  width: 100%;
  height: 100%;

  .jsoncrack-space,
  .jsoncrack-space:active {
    cursor: url("/assets/cursor.svg"), auto;
  }
`;

interface GraphProps {
  isWidget?: boolean;
}

export const GraphView = ({ isWidget = false }: GraphProps) => {
  const setViewPort = useGraph(state => state.setViewPort);
  const direction = useGraph(state => state.direction);
  const setSelectedNode = useGraph(state => state.setSelectedNode);
  const gesturesEnabled = useConfig(state => state.gesturesEnabled);
  const rulersEnabled = useConfig(state => state.rulersEnabled);
  const darkmodeEnabled = useConfig(state => state.darkmodeEnabled);
  const json = useJson(state => state.json);
  const setVisible = useModal(state => state.setVisible);

  const callback = React.useCallback(() => {
    const canvas = document.querySelector(".jsoncrack-canvas") as HTMLDivElement | null;
    canvas?.classList.add("dragging");
  }, []);

  const bindLongPress = useLongPress(callback, {
    threshold: 150,
    onFinish: () => {
      const canvas = document.querySelector(".jsoncrack-canvas") as HTMLDivElement | null;
      canvas?.classList.remove("dragging");
    },
  });

  const blurOnClick = React.useCallback(() => {
    if ("activeElement" in document) {
      (document.activeElement as HTMLElement | null)?.blur();
    }
  }, []);

  const handleNodeClick = React.useCallback(
    (node: NodeData) => {
      setSelectedNode(node);
      setVisible("NodeModal", true);
    },
    [setSelectedNode, setVisible]
  );

  const maxVisibleNodes = Number.isFinite(SUPPORTED_LIMIT) ? SUPPORTED_LIMIT : 1500;

  return (
    <Box pos="relative" h="100%" w="100%">
      {!isWidget && <OptionsMenu />}
      {!isWidget && <SecureInfo />}
      <ZoomControl />
      <StyledEditorWrapper
        $widget={isWidget}
        onContextMenu={event => event.preventDefault()}
        onClick={blurOnClick}
        {...bindLongPress()}
      >
        <JSONCrack
          key={[direction, gesturesEnabled, rulersEnabled].join("-")}
          json={json}
          theme={darkmodeEnabled ? "dark" : "light"}
          layoutDirection={direction}
          showControls={false}
          showGrid={rulersEnabled}
          trackpadZoom={gesturesEnabled}
          maxRenderableNodes={maxVisibleNodes}
          centerOnLayout
          onViewportCreate={setViewPort}
          onNodeClick={handleNodeClick}
          renderNodeLimitExceeded={() => <NotSupported />}
        />
      </StyledEditorWrapper>
    </Box>
  );
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/lib/jsonParser.ts
================================================
/**
 * Copyright (c) JSON Crack
 * This source code is licensed under the Apache 2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */
import { parseTree, getNodePath, type Node } from "jsonc-parser";
import type { EdgeData, NodeData, NodeRow } from "jsoncrack-react";
import { calculateNodeSize } from "./utils/calculateNodeSize";

export type Graph = {
  nodes: NodeData[];
  edges: EdgeData[];
};

export const parser = (json: string): Graph => {
  const jsonTree = parseTree(json);
  if (!jsonTree) return { nodes: [], edges: [] };

  const nodes: NodeData[] = [];
  const edges: EdgeData[] = [];
  let nodeId = 1;
  let edgeId = 1;

  function traverse(node: Node, parentId?: string) {
    const id = String(nodeId++);
    const text: NodeRow[] = [];

    // If parentId is provided, create an edge from parentId to the current node id
    if (parentId !== undefined && node.parent?.type === "array") {
      edges.push({
        id: String(edgeId++),
        from: parentId,
        to: id,
        text: "",
      });
    }

    const isArray = node.type === "array";
    const isRootArray = !node.parent || node.parent.type === "array";
    if (isArray && isRootArray) {
      const { width, height } = calculateNodeSize(`[${node.children?.length ?? "0"} items]`);
      nodes.push({
        id,
        text: [
          {
            key: null,
            value: `[${node.children?.length ?? 0} items]`,
            type: "array",
            childrenCount: node.children?.length,
          },
        ],
        width,
        height,
        path: [],
      });

      node.children?.forEach(child => {
        traverse(child, id);
      });

      return id;
    }

    node.children?.forEach(child => {
      if (!child.children || !child.children[1]) return traverse(child, id);

      const key = child.children[0].value ?? null;
      const valueNode = child.children[1];
      const type = valueNode.type;

      if (type === "array") {
        const targetIds: string[] = [];

        valueNode.children?.forEach(arrayChild => {
          const arrayChildId = traverse(arrayChild, undefined);
          if (arrayChildId) targetIds.push(arrayChildId);
        });

        text.push({
          key,
          value: valueNode.value,
          type,
          to: targetIds.length > 0 ? targetIds : undefined,
          childrenCount: valueNode.children?.length,
        });

        targetIds.forEach(targetId => {
          edges.push({
            id: String(edgeId++),
            from: id,
            to: targetId,
            text: key ?? null,
          });
        });
      } else if (type === "object") {
        const objectNodeId = traverse(valueNode, id);
        text.push({
          key,
          value: valueNode.value,
          type,
          childrenCount: Object.keys(valueNode.children ?? {}).length,
          ...(objectNodeId && { to: [objectNodeId] }),
        });

        if (objectNodeId) {
          edges.push({
            id: String(edgeId++),
            from: id,
            to: objectNodeId,
            text: key ?? null,
          });
        }
      } else {
        text.push({
          key,
          value: valueNode.value,
          type,
        });
      }
    });

    // to handle case where empty object inside array [{}]
    if (node.parent?.type === "array" && node.type === "object" && node.children?.length === 0) {
      text.push({
        key: null,
        value: "{0 keys}",
        type: "object",
        childrenCount: 0,
      });
    }

    const appendParentKey = () => {
      const getParentKey = (targetNode: any) => {
        const path = getNodePath(targetNode);
        return path?.pop()?.toString();
      };

      if (!node.parent) {
        return { parentKey: getParentKey(node), parentType: node.type };
      }

      if (node.parent.type === "array") {
        return { parentKey: getParentKey(node.parent), parentType: "array" };
      }

      if (node.parent.type === "property") {
        return { parentKey: getParentKey(node), parentType: "object" };
      }

      return {
        parentKey: getParentKey(node),
        parentType: node.parent.type.replace("property", "object"),
      };
    };

    // its for singular text like string, number, boolean, null
    if (text.length === 0) {
      if (typeof node.value === "undefined") return;
      const { width, height } = calculateNodeSize(node.value);

      nodes.push({
        id,
        text: [
          {
            key: null,
            value: node.value,
            type: node.type,
          },
        ],
        width,
        height,
        path: getNodePath(node),
        ...appendParentKey(),
      });
    } else {
      let t: string | [string, string][];

      if (text.some(t => t.key !== null)) {
        t = text.map(t => {
          const keyStr = t.key === null ? "" : t.key;
          if (t.type === "object") return [keyStr, `{${t.childrenCount ?? 0} keys}`];
          if (t.type === "array") return [keyStr, `[${t.childrenCount ?? 0} items]`];
          if (t.value === null) return [keyStr, "null"];

          return [keyStr, `${t.value}`];
        });
      } else {
        t = `${text[0].value}`;
      }

      const { width, height } = calculateNodeSize(t);
      nodes.push({
        id,
        text,
        width,
        height,
        path: getNodePath(node),
        ...appendParentKey(),
      });
    }

    return id; // Return the current id for referencing in the parent node
  }

  traverse(jsonTree);
  return { nodes, edges };
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/lib/utils/calculateNodeSize.ts
================================================
import { NODE_DIMENSIONS } from "../../../../../../constants/graph";

type Text = number | string | [string, string][];
type Size = { width: number; height: number };

const calculateLines = (text: Text): string => {
  if (Array.isArray(text)) {
    return text.map(([k, v]) => `${k}: ${JSON.stringify(v).slice(0, 80)}`).join("\n");
  }

  return `${text}`;
};

const calculateWidthAndHeight = (str: string, single = false) => {
  if (!str) return { width: 45, height: 45 };

  const dummyElement = document.createElement("div");
  dummyElement.style.whiteSpace = single ? "nowrap" : "pre-wrap";
  dummyElement.innerText = str;
  dummyElement.style.fontSize = "12px";
  dummyElement.style.width = "fit-content";
  dummyElement.style.padding = "0 10px";
  dummyElement.style.fontWeight = "500";
  dummyElement.style.fontFamily = "monospace";
  document.body.appendChild(dummyElement);

  const clientRect = dummyElement.getBoundingClientRect();
  const lines = str.split("\n").length;

  const width = clientRect.width + 4;
  // Use parent height for single line nodes that are parents
  const height = single ? NODE_DIMENSIONS.PARENT_HEIGHT : lines * NODE_DIMENSIONS.ROW_HEIGHT;

  document.body.removeChild(dummyElement);
  return { width, height };
};

const sizeCache = new Map<Text, Size>();

// clear cache every 2 mins
setInterval(() => sizeCache.clear(), 120_000);

export const calculateNodeSize = (text: Text, isParent = false) => {
  const cacheKey = [text, isParent].toString();

  // check cache if data already exists
  if (sizeCache.has(cacheKey)) {
    const size = sizeCache.get(cacheKey);
    if (size) return size;
  }

  const lines = calculateLines(text);
  const sizes = calculateWidthAndHeight(lines, typeof text === "string");

  if (isParent) sizes.width += 80;
  if (sizes.width > 700) sizes.width = 700;

  sizeCache.set(cacheKey, sizes);
  return sizes;
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/lib/utils/getChildrenEdges.ts
================================================
import type { EdgeData, NodeData } from "jsoncrack-react";

export const getChildrenEdges = (nodes: NodeData[], edges: EdgeData[]): EdgeData[] => {
  const nodeIds = nodes.map(node => node.id);

  return edges.filter(
    edge => nodeIds.includes(edge.from as string) || nodeIds.includes(edge.to as string)
  );
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/lib/utils/getOutgoers.ts
================================================
import type { EdgeData, NodeData } from "jsoncrack-react";

type Outgoers = [NodeData[], string[]];

export const getOutgoers = (
  nodeId: string,
  nodes: NodeData[],
  edges: EdgeData[],
  parent: string[] = []
): Outgoers => {
  const outgoerNodes: NodeData[] = [];
  const matchingNodes: string[] = [];

  if (parent.includes(nodeId)) {
    const initialParentNode = nodes.find(n => n.id === nodeId);

    if (initialParentNode) outgoerNodes.push(initialParentNode);
  }

  const findOutgoers = (currentNodeId: string) => {
    const outgoerIds = edges.filter(e => e.from === currentNodeId).map(e => e.to);
    const nodeList = nodes.filter(n => {
      if (parent.includes(n.id) && !matchingNodes.includes(n.id)) matchingNodes.push(n.id);
      return outgoerIds.includes(n.id) && !parent.includes(n.id);
    });

    outgoerNodes.push(...nodeList);
    nodeList.forEach(node => findOutgoers(node.id));
  };

  findOutgoers(nodeId);
  return [outgoerNodes, matchingNodes];
};


================================================
FILE: apps/www/src/features/editor/views/GraphView/stores/useGraph.ts
================================================
import type { LayoutDirection, NodeData } from "jsoncrack-react";
import type { ViewPort } from "react-zoomable-ui";
import { create } from "zustand";

export interface Graph {
  viewPort: ViewPort | null;
  direction: LayoutDirection;
  fullscreen: boolean;
  selectedNode: NodeData | null;
}

const initialStates: Graph = {
  viewPort: null,
  direction: "RIGHT",
  fullscreen: false,
  selectedNode: null,
};

interface GraphActions {
  setDirection: (direction: LayoutDirection) => void;
  setViewPort: (ref: ViewPort) => void;
  setSelectedNode: (nodeData: NodeData | null) => void;
  focusFirstNode: () => void;
  toggleFullscreen: (value: boolean) => void;
  zoomIn: () => void;
  zoomOut: () => void;
  centerView: () => void;
}

const useGraph = create<Graph & GraphActions>((set, get) => ({
  ...initialStates,
  setSelectedNode: nodeData => set({ selectedNode: nodeData }),
  setDirection: (direction = "RIGHT") => {
    set({ direction });
    setTimeout(() => get().centerView(), 200);
  },
  focusFirstNode: () => {
    const rootNode = document.querySelector("g[id$='node-1']");
    get().viewPort?.camera?.centerFitElementIntoView(rootNode as HTMLElement, {
      elementExtraMarginForZoom: 100,
    });
  },
  zoomIn: () => {
    const viewPort = get().viewPort;
    viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor + 0.1);
  },
  zoomOut: () => {
    const viewPort = get().viewPort;
    viewPort?.camera?.recenter(viewPort.centerX, viewPort.centerY, viewPort.zoomFactor - 0.1);
  },
  centerView: () => {
    const viewPort = get().viewPort;
    viewPort?.updateContainerSize();

    const canvas = document.querySelector(".jsoncrack-canvas") as HTMLElement | null;
    if (canvas) {
      viewPort?.camera?.centerFitElementIntoView(canvas);
    }
  },
  toggleFullscreen: fullscreen => set({ fullscreen }),
  setViewPort: viewPort => set({ viewPort }),
}));

export default useGraph;


================================================
FILE: apps/www/src/features/editor/views/TreeView/Label.tsx
================================================
import React from "react";
import type { DefaultTheme } from "styled-components";
import { styled } from "styled-components";
import type { KeyPath } from "react-json-tree";

interface LabelProps {
  keyPath: KeyPath;
  nodeType: string;
}

function getLabelColor({ $type, theme }: { $type?: string; theme: DefaultTheme }) {
  if ($type === "Object") return theme.NODE_COLORS.PARENT_OBJ;
  if ($type === "Array") return theme.NODE_COLORS.PARENT_ARR;
  return theme.NODE_COLORS.PARENT_OBJ;
}

const StyledLabel = styled.span<{ $nodeType?: string }>`
  color: ${({ theme, $nodeType }) => getLabelColor({ theme, $type: $nodeType })};

  &:hover {
    filter: brightness(1.5);
    transition: filter 0.2s ease-in-out;
  }
`;

export const Label = ({ keyPath, nodeType }: LabelProps) => {
  return <StyledLabel $nodeType={nodeType}>{keyPath[0]}:</StyledLabel>;
};


================================================
FILE: apps/www/src/features/editor/views/TreeView/Value.tsx
================================================
import React from "react";
import type { DefaultTheme } from "styled-components";
import { useTheme } from "styled-components";
import { TextRenderer } from "../GraphView/CustomNode/TextRenderer";

type TextColorFn = {
  theme: DefaultTheme;
  $value?: string | unknown;
};

function getValueColor({ $value, theme }: TextColorFn) {
  if ($value && !Number.isNaN(+$value)) return theme.NODE_COLORS.INTEGER;
  if ($value === "true") return theme.NODE_COLORS.BOOL.TRUE;
  if ($value === "false") return theme.NODE_COLORS.BOOL.FALSE;
  if ($value === "null") return theme.NODE_COLORS.NULL;

  // default
  return theme.NODE_COLORS.NODE_VALUE;
}

interface ValueProps {
  valueAsString: unknown;
  value: unknown;
}

export const Value = (props: ValueProps) => {
  const theme = useTheme();
  const { valueAsString, value } = props;

  return (
    <span
      style={{
        color: getValueColor({
          theme,
          $value: valueAsString,
        }),
      }}
    >
      <TextRenderer>{JSON.stringify(value)}</TextRenderer>
    </span>
  );
};


================================================
FILE: apps/www/src/features/editor/views/TreeView/index.tsx
================================================
import React from "react";
import { useTheme } from "styled-components";
import { JSONTree } from "react-json-tree";
import useJson from "../../../../store/useJson";
import { Label } from "./Label";
import { Value } from "./Value";

export const TreeView = () => {
  const theme = useTheme();
  const json = useJson(state => state.json);

  return (
    <JSONTree
      hideRoot
      data={JSON.parse(json)}
      valueRenderer={(valueAsString, value) => <Value {...{ valueAsString, value }} />}
      labelRenderer={(keyPath, nodeType) => <Label {...{ keyPath, nodeType }} />}
      theme={{
        extend: {
          overflow: "scroll",
          height: "100%",
          scheme: "monokai",
          author: "wimer hazenberg (http://www.monokai.nl)",
          base00: theme.GRID_BG_COLOR,
        },
      }}
    />
  );
};


================================================
FILE: apps/www/src/features/modals/DownloadModal/index.tsx
================================================
import React from "react";
import type { ModalProps } from "@mantine/core";
import {
  ColorPicker,
  TextInput,
  SegmentedControl,
  Group,
  Modal,
  Button,
  Divider,
  ColorInput,
} from "@mantine/core";
import { toBlob, toJpeg, toPng, toSvg } from "html-to-image";
import { event as gaEvent } from "nextjs-google-analytics";
import toast from "react-hot-toast";
import { FiCopy, FiDownload } from "react-icons/fi";

enum Extensions {
  SVG = "svg",
  PNG = "png",
  JPEG = "jpeg",
}

const getDownloadFormat = (format: Extensions) => {
  switch (format) {
    case Extensions.SVG:
      return toSvg;
    case Extensions.PNG:
      return toPng;
    case Extensions.JPEG:
      return toJpeg;
  }
};

const swatches = [
  "#B80000",
  "#DB3E00",
  "#FCCB00",
  "#008B02",
  "#006B76",
  "#1273DE",
  "#004DCF",
  "#5300EB",
  "#EB9694",
  "#FAD0C3",
  "#FEF3BD",
  "#C1E1C5",
  "#BEDADC",
  "#C4DEF6",
  "#BED3F3",
  "#D4C4FB",
  "transparent",
];

function downloadURI(uri: string, name: string) {
  const link = document.createElement("a");

  link.download = name;
  link.href = uri;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

const getExportElement = () =>
  (document.querySelector(".jsoncrack-canvas") as HTMLElement | null) ??
  (document.querySelector("svg[id*='ref']") as HTMLElement | null);

export const DownloadModal = ({ opened, onClose }: ModalProps) => {
  const [extension, setExtension] = React.useState(Extensions.PNG);
  const [fileDetails, setFileDetails] = React.useState({
    filename: "jsoncrack.com",
    backgroundColor: "#FFFFFF",
    quality: 1,
  });

  const clipboardImage = async () => {
    try {
      toast.loading("Copying to clipboard...", { id: "toastClipboard" });

      const imageElement = getExportElement();
      if (!imageElement) {
        toast.error("Canvas not found.");
        return;
      }
      const imageOptions = {
        quality: fileDetails.quality,
        backgroundColor: fileDetails.backgroundColor,
        skipFonts: true,
      };

      const blob = await toBlob(imageElement, imageOptions);

      if (!blob) return;

      await navigator.clipboard?.write([
        new ClipboardItem({
          [blob.type]: blob,
        }),
      ]);

      toast.success("Copied to clipboard");
      gaEvent("clipboard_img");
    } catch (error) {
      if (error instanceof Error && error.name === "NotAllowedError") {
        toast.error(
          "Clipboard write permission denied. Please allow clipboard access in your browser settings."
        );
      } else {
        toast.error("Failed to copy to clipboard");
      }
    } finally {
      toast.dismiss("toastClipboard");
      onClose();
    }
  };

  const exportAsImage = async () => {
    try {
      toast.loading("Downloading...", { id: "toastDownload" });

      const imageElement = getExportElement();
      if (!imageElement) {
        toast.error("Canvas not found.");
        return;
      }
      const imageOptions = {
        quality: fileDetails.quality,
        backgroundColor: fileDetails.backgroundColor,
        skipFonts: true,
      };

      const dataURI = await getDownloadFormat(extension)(imageElement, imageOptions);

      downloadURI(dataURI, `${fileDetails.filename}.${extension}`);
      gaEvent("download_img", { label: extension });
    } catch {
      toast.error("Failed to download image!");
    } finally {
      toast.dismiss("toastDownload");
      onClose();
    }
  };

  const updateDetails = (key: keyof typeof fileDetails, value: string | number) =>
    setFileDetails({ ...fileDetails, [key]: value });

  return (
    <Modal opened={opened} onClose={onClose} title="Download Image" centered>
      <TextInput
        label="File Name"
        value={fileDetails.filename}
        onChange={e => updateDetails("filename", e.target.value)}
        mb="lg"
      />
      <SegmentedControl
        value={extension}
        onChange={e => setExtension(e as Extensions)}
        fullWidth
        data={[
          { label: "PNG", value: Extensions.PNG },
          { label: "JPEG", value: Extensions.JPEG },
          { label: "SVG", value: Extensions.SVG },
        ]}
        mb="lg"
      />
      <ColorInput
        label="Background Color"
        value={fileDetails.backgroundColor}
        onChange={color => updateDetails("backgroundColor", color)}
        withEyeDropper={false}
        mb="lg"
      />
      <ColorPicker
        format="rgba"
        value={fileDetails.backgroundColor}
        onChange={color => updateDetails("backgroundColor", color)}
        swatches={swatches}
        withPicker={false}
        fullWidth
      />
      <Divider my="xs" />
      <Group justify="right">
        <Button leftSection={<FiCopy />} onClick={clipboardImage}>
          Clipboard
        </Button>
        <Button color="green" leftSection={<FiDownload />} onClick={exportAsImage}>
          Download
        </Button>
      </Group>
    </Modal>
  );
};


================================================
FILE: apps/www/src/features/modals/ImportModal/index.tsx
================================================
import React from "react";
import type { ModalProps } from "@mantine/core";
import { Modal, Group, Button, TextInput, Stack, Paper, Text } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import { event as gaEvent } from "nextjs-google-analytics";
import toast from "react-hot-toast";
import { AiOutlineUpload } from "react-icons/ai";
import type { FileFormat } from "../../../enums/file.enum";
import useFile from "../../../store/useFile";

export const ImportModal = ({ opened, onClose }: ModalProps) => {
  const [url, setURL] = React.useState("");
  const [file, setFile] = React.useState<File | null>(null);

  const setContents = useFile(state => state.setContents);
  const setFormat = useFile(state => state.setFormat);

  const handleImportFile = () => {
    if (url) {
      setFile(null);

      toast.loading("Loading...", { id: "toastFetch" });
      gaEvent("fetch_url");

      return fetch(url)
        .then(res => res.json())
        .then(json => {
          setContents({ contents: JSON.stringify(json, null, 2) });
          onClose();
        })
        .catch(() => toast.error("Failed to fetch JSON!"))
        .finally(() => toast.dismiss("toastFetch"));
    } else if (file) {
      const lastIndex = file.name.lastIndexOf(".");
      const format = file.name.substring(lastIndex + 1);
      setFormat(format as FileFormat);

      file.text().then(text => {
        setContents({ contents: text });
        setFile(null);
        setURL("");
        onClose();
      });

      gaEvent("import_file", { label: format });
    }
  };

  return (
    <Modal
      title="Import File"
      opened={opened}
      onClose={() => {
        setFile(null);
        setURL("");
        onClose();
      }}
      centered
    >
      <Stack py="sm">
        <TextInput
          value={url}
          onChange={e => setURL(e.target.value)}
          type="url"
          placeholder="URL of JSON to fetch"
          data-autofocus
        />
        <Paper radius="md" style={{ cursor: "pointer" }}>
          <Dropzone
            onDrop={files => setFile(files[0])}
            onReject={files => toast.error(`Unable to load file ${files[0].file.name}`)}
            maxFiles={1}
            p="md"
            accept={["application/json", "application/x-yaml", "text/csv", "application/xml"]}
          >
            <Stack justify="center" align="center" gap="sm" mih={220}>
              <AiOutlineUpload size={48} />
              <Text fw="bold">Drop here or click to upload files</Text>
              <Text c="dimmed" fz="sm">
                {file?.name ?? "None"}
              </Text>
            </Stack>
          </Dropzone>
        </Paper>
      </Stack>
      <Group justify="right">
        <Button onClick={handleImportFile} disabled={!(file || url)}>
          Import
        </Button>
      </Group>
    </Modal>
  );
};


================================================
FILE: apps/www/src/features/modals/JPathModal/index.tsx
================================================
import React from "react";
import type { ModalProps } from "@mantine/core";
import { Stack, Modal, Button, Text, Anchor, Group, TextInput } from "@mantine/core";
import { JSONPath } from "jsonpath-plus";
import { event as gaEvent } from "nextjs-google-analytics";
import toast from "react-hot-toast";
import { VscLinkExternal } from "react-icons/vsc";
import useFile from "../../../store/useFile";
import useJson from "../../../store/useJson";

export const JPathModal = ({ opened, onClose }: ModalProps) => {
  const getJson = useJson(state => state.getJson);
  const setContents = useFile(state => state.setContents);
  const [query, setQuery] = React.useState("");

  const evaluteJsonPath = () => {
    try {
      const json = getJson();
      const result = JSONPath({ path: query, json: JSON.parse(json) });

      setContents({ contents: JSON.stringify(result, null, 2) });
      gaEvent("run_json_path");
      onClose();
    } catch (error) {
      if (error instanceof Error) toast.error(error.message);
    }
  };

  return (
    <Modal title="JSON Path" size="lg" opened={opened} onClose={onClose} centered>
      <Stack>
        <Text fz="sm">
          JsonPath expressions always refer to a JSON structure in the same way as XPath expression
          are used in combination with an XML document. The &quot;root member object&quot; in
          JsonPath is always referred to as $ regardless if it is an object or array.
          <br />
          <Anchor
            fz="sm"
            target="_blank"
            href="https://docs.oracle.com/cd/E60058_01/PDF/8.0.8.x/8.0.8.0.0/PMF_HTML/JsonPath_Expressions.htm"
            rel="noopener noreferrer"
          >
            Read documentation. <VscLinkExternal />
          </Anchor>
        </Text>
        <TextInput
          value={query}
          onChange={e => setQuery(e.currentTarget.value)}
          placeholder="Enter JSON Path..."
          data-autofocus
        />
        <Group justify="right">
          <Button onClick={evaluteJsonPath} disabled={!query.length}>
            Run
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
};


================================================
FILE: apps/www/src/features/modals/JQModal/index.tsx
================================================
import React from "react";
import type { ModalProps } from "@mantine/core";
import { Stack, Modal, Button, Text, Anchor, Group, TextInput } from "@mantine/core";
import { VscLinkExternal } from "react-icons/vsc";
import useJsonQuery from "../../../hooks/useJsonQuery";

export const JQModal = ({ opened, onClose }: ModalProps) => {
  const { updateJson } = useJsonQuery();
  const [query, setQuery] = React.useState("");

  return (
    <Modal title="JSON Query" size="lg" opened={opened} onClose={onClose} centered>
      <Stack>
        <Text fz="sm">
          jq is a lightweight and flexible command-line JSON processor. JSON Crack uses simplified
          version of jq, not all features are supported.
          <br />
          <Anchor
            fz="sm"
            target="_blank"
            href="https://jqlang.github.io/jq/manual/"
            rel="noopener noreferrer"
          >
            Read documentation. <VscLinkExternal />
          </Anchor>
        </Text>
        <TextInput
          leftSection="jq"
          placeholder="Enter jq query"
          value={query}
          onChange={e => setQuery(e.currentTarget.value)}
        />
        <Group justify="right">
          <Button onClick={() => updateJson(query, onClose)}>Display on Graph</Button>
        </Group>
      </Stack>
    </Modal>
  );
};


================================================
FILE: apps/www/src/features/modals/ModalController.tsx
================================================
import React from "react";
import * as ModalComponents from ".";
import { useModal } from "../../store/useModal";
import { modals, type ModalName } from "./modalTypes";

const Modal = ({ modalKey }: { modalKey: ModalName }) => {
  const opened = useModal(state => state[modalKey]);
  const setVisible = useModal(state => state.setVisible);
  const ModalComponent = ModalComponents[modalKey];

  return <ModalComponent opened={opened} onClose={() => setVisible(modalKey, false)} />;
};

const ModalController = () => {
  return modals.map(modal => <Modal key={modal} modalKey={modal} />);
};

export default ModalController;


================================================
FILE: apps/www/src/features/modals/NodeModal/index.tsx
================================================
import React from "react";
import type { ModalProps } from "@mantine/core";
import { Modal, Stack, Text, ScrollArea, Flex, CloseButton } from "@mantine/core";
import { CodeHighlight } from "@mantine/code-highlight";
import type { NodeData } from "jsoncrack-react";
import useGraph from "../../editor/views/GraphView/stores/useGraph";

// return object from json removing array and object fields
const normalizeNodeData = (nodeRows: NodeData["text"]) => {
  if (!nodeRows || nodeRows.length === 0) return "{}";
  if (nodeRows.length === 1 && !nodeRows[0].key) return `${nodeRows[0].value}`;

  const obj = {};
  nodeRows?.forEach(row => {
    if (row.type !== "array" && row.type !== "object") {
      if (row.key) obj[row.key] = row.value;
    }
  });
  return JSON.stringify(obj, null, 2);
};

// return json path in the format $["customer"]
const jsonPathToString = (path?: NodeData["path"]) => {
  if (!path || path.length === 0) return "$";
  const segments = path.map(seg => (typeof seg === "number" ? seg : `"${seg}"`));
  return `$[${segments.join("][")}]`;
};

export const NodeModal = ({ opened, onClose }: ModalProps) => {
  const nodeData = useGraph(state => state.selectedNode);

  return (
    <Modal size="auto" opened={opened} onClose={onClose} centered withCloseButton={false}>
      <Stack pb="sm" gap="sm">
        <Stack gap="xs">
          <Flex justify="space-between" align="center">
            <Text fz="xs" fw={500}>
              Content
            </Text>
            <CloseButton onClick={onClose} />
          </Flex>
          <ScrollArea.Autosize mah={250} maw={600}>
            <CodeHighlight
              code={normalizeNodeData(nodeData?.text ?? [])}
              miw={350}
              maw={600}
              language="json"
              withCopyButton
            />
          </ScrollArea.Autosize>
        </Stack>
        <Text fz="xs" fw={500}>
          JSON Path
        </Text>
        <ScrollArea.Autosize maw={600}>
          <CodeHighlight
            code={jsonPathToString(nodeData?.path)}
            miw={350}
            mah={250}
            language="json"
            copyLabel="Copy to clipboard"
            copiedLabel="Copied to clipboard"
            withCopyButton
          />
        </ScrollArea.Autosize>
      </Stack>
    </Modal>
  );
};


================================================
FILE: apps/www/src/features/modals/SchemaModal/index.tsx
================================================
import React from "react";
import type { ModalProps } from "@mantine/core";
import { Stack, Modal, Button, Text, Anchor, Group, Paper } from "@mantine/core";
import Editor from "@monaco-editor/react";
import { event as gaEvent } from "nextjs-google-analytics";
import { toast } from "react-hot-toast";
import { VscLinkExternal } from "react-icons/vsc";
import useConfig from "../../../store/useConfig";
import useFile from "../../../store/useFile";

export const SchemaModal = ({ opened, onClose }: ModalProps) => {
  const setJsonSchema = useFile(state => state.setJsonSchema);
  const darkmodeEnabled = useConfig(state => (state.darkmodeEnabled ? "vs-dark" : "light"));
  const [schema, setSchema] = React.useState(
    JSON.stringify(
      {
        $schema: "http://json-schema.org/draft-04/schema#",
        title: "Product",
        description: "A product from catalog",
        type: "object",
        properties: {
          id: {
            description: "The unique identifier for a product",
            type: "integer",
          },
        },
        required: ["id"],
      },
      null,
      2
    )
  );

  const onApply = () => {
    try {
      const parsedSchema = JSON.parse(schema);
      setJsonSchema(parsedSchema);

      gaEvent("apply_json_schema");
      toast.success("Applied schema!");
      onClose();
    } catch {
      toast.error("Invalid Schema");
    }
  };

  const onClear = () => {
    setJsonSchema(null);
    setSchema("");
    toast("Disabled JSON Schema");
    onClose();
  };

  return (
    <Modal title="JSON Schema" size="lg" opened={opened} onClose={onClose} centered>
      <Stack>
        <Text fz="sm">Any validation failures are shown at the bottom toolbar of pane.</Text>
        <Anchor
          fz="sm"
          target="_blank"
          href="https://niem.github.io/json/sample-schema/"
          rel="noopener noreferrer"
        >
          View Examples <VscLinkExternal />
        </Anchor>
        <Paper withBorder radius="sm" style={{ overflow: "hidden" }}>
          <Editor
            value={schema ?? ""}
            theme={darkmodeEnabled}
            onChange={e => setSchema(e!)}
            height={300}
            language="json"
            options={{
              formatOnPaste: true,
              tabSize: 2,
              formatOnType: true,
              scrollBeyondLastLine: false,
              stickyScroll: { enabled: false },
              minimap: { enabled: false },
            }}
          />
        </Paper>
        <Group p="0" justify="right">
          <Button variant="subtle" color="gray" onClick={onClear} disabled={!schema}>
            Clear
          </Button>
          <Button variant="default" onClick={onApply} disabled={!schema}>
            Apply
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
};


================================================
FILE: apps/www/src/features/modals/TypeModal/index.tsx
================================================
import React from "react";
import type { ModalProps } from "@mantine/core";
import { Stack, Modal, Select, ScrollArea } from "@mantine/core";
import { CodeHighlight } from "@mantine/code-highlight";
import { event as gaEvent } from "nextjs-google-analytics";
import useJson from "../../../store/useJson";

enum Language {
  TypeScript = "typescript",
  TypeScript_Combined = "typescript/typ
Download .txt
gitextract_0apmokt3/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── deploy.yml
│       └── pull-request.yml
├── .gitignore
├── .npmrc
├── .vscode/
│   ├── launch.json
│   └── tasks.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── apps/
│   ├── vscode/
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc
│   │   ├── .vscodeignore
│   │   ├── LICENSE.md
│   │   ├── README.md
│   │   ├── esbuild.config.mjs
│   │   ├── eslint.config.mjs
│   │   ├── ext-src/
│   │   │   ├── extension.ts
│   │   │   └── webview.ts
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── App.tsx
│   │   │   ├── components/
│   │   │   │   └── NodeModal.tsx
│   │   │   ├── env.d.ts
│   │   │   ├── global.css
│   │   │   └── index.tsx
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   └── www/
│       ├── .dockerignore
│       ├── .gitignore
│       ├── .prettierignore
│       ├── .prettierrc
│       ├── Dockerfile
│       ├── LICENSE.md
│       ├── docker-compose.yml
│       ├── eslint.config.mjs
│       ├── next-env.d.ts
│       ├── next-sitemap.config.js
│       ├── next.config.js
│       ├── nginx.conf
│       ├── package.json
│       ├── public/
│       │   ├── .nojekyll
│       │   ├── CNAME
│       │   ├── manifest.json
│       │   ├── robots.txt
│       │   ├── sitemap-0.xml
│       │   └── sitemap.xml
│       ├── shims/
│       │   └── empty.ts
│       ├── src/
│       │   ├── constants/
│       │   │   ├── globalStyle.ts
│       │   │   ├── graph.ts
│       │   │   ├── seo.ts
│       │   │   └── theme.ts
│       │   ├── data/
│       │   │   ├── example.json
│       │   │   ├── faq.json
│       │   │   ├── privacy.json
│       │   │   └── terms.json
│       │   ├── enums/
│       │   │   ├── file.enum.ts
│       │   │   └── viewMode.enum.ts
│       │   ├── features/
│       │   │   ├── Banner.tsx
│       │   │   ├── editor/
│       │   │   │   ├── BottomBar.tsx
│       │   │   │   ├── ExternalMode.tsx
│       │   │   │   ├── FullscreenDropzone.tsx
│       │   │   │   ├── LiveEditor.tsx
│       │   │   │   ├── TextEditor.tsx
│       │   │   │   ├── Toolbar/
│       │   │   │   │   ├── FileMenu.tsx
│       │   │   │   │   ├── SearchInput.tsx
│       │   │   │   │   ├── ThemeToggle.tsx
│       │   │   │   │   ├── ToolsMenu.tsx
│       │   │   │   │   ├── ViewMenu.tsx
│       │   │   │   │   ├── index.tsx
│       │   │   │   │   └── styles.ts
│       │   │   │   └── views/
│       │   │   │       ├── GraphView/
│       │   │   │       │   ├── CustomEdge/
│       │   │   │       │   │   └── index.tsx
│       │   │   │       │   ├── CustomNode/
│       │   │   │       │   │   ├── ObjectNode.tsx
│       │   │   │       │   │   ├── TextNode.tsx
│       │   │   │       │   │   ├── TextRenderer.tsx
│       │   │   │       │   │   ├── index.tsx
│       │   │   │       │   │   └── styles.tsx
│       │   │   │       │   ├── NotSupported.tsx
│       │   │   │       │   ├── OptionsMenu.tsx
│       │   │   │       │   ├── SecureInfo.tsx
│       │   │   │       │   ├── ZoomControl.tsx
│       │   │   │       │   ├── index.tsx
│       │   │   │       │   ├── lib/
│       │   │   │       │   │   ├── jsonParser.ts
│       │   │   │       │   │   └── utils/
│       │   │   │       │   │       ├── calculateNodeSize.ts
│       │   │   │       │   │       ├── getChildrenEdges.ts
│       │   │   │       │   │       └── getOutgoers.ts
│       │   │   │       │   └── stores/
│       │   │   │       │       └── useGraph.ts
│       │   │   │       └── TreeView/
│       │   │   │           ├── Label.tsx
│       │   │   │           ├── Value.tsx
│       │   │   │           └── index.tsx
│       │   │   └── modals/
│       │   │       ├── DownloadModal/
│       │   │       │   └── index.tsx
│       │   │       ├── ImportModal/
│       │   │       │   └── index.tsx
│       │   │       ├── JPathModal/
│       │   │       │   └── index.tsx
│       │   │       ├── JQModal/
│       │   │       │   └── index.tsx
│       │   │       ├── ModalController.tsx
│       │   │       ├── NodeModal/
│       │   │       │   └── index.tsx
│       │   │       ├── SchemaModal/
│       │   │       │   └── index.tsx
│       │   │       ├── TypeModal/
│       │   │       │   └── index.tsx
│       │   │       ├── index.ts
│       │   │       └── modalTypes.ts
│       │   ├── hooks/
│       │   │   ├── useFocusNode.ts
│       │   │   └── useJsonQuery.ts
│       │   ├── layout/
│       │   │   ├── ConverterLayout/
│       │   │   │   ├── PageLinks.tsx
│       │   │   │   ├── ToolPage.tsx
│       │   │   │   └── options.ts
│       │   │   ├── JSONCrackBrandLogo.tsx
│       │   │   ├── Landing/
│       │   │   │   ├── FAQ.tsx
│       │   │   │   ├── Features.tsx
│       │   │   │   ├── HeroPreview.tsx
│       │   │   │   ├── HeroSection.tsx
│       │   │   │   ├── Section1.tsx
│       │   │   │   ├── Section2.tsx
│       │   │   │   └── Section3.tsx
│       │   │   ├── PageLayout/
│       │   │   │   ├── Footer.tsx
│       │   │   │   ├── Navbar.tsx
│       │   │   │   └── index.tsx
│       │   │   └── TypeLayout/
│       │   │       ├── PageLinks.tsx
│       │   │       └── TypegenWrapper.tsx
│       │   ├── lib/
│       │   │   └── utils/
│       │   │       ├── generateType.ts
│       │   │       ├── helpers.ts
│       │   │       ├── json2go.js
│       │   │       ├── jsonAdapter.ts
│       │   │       ├── mantineColorScheme.ts
│       │   │       └── search.ts
│       │   ├── pages/
│       │   │   ├── 404.tsx
│       │   │   ├── _app.tsx
│       │   │   ├── _document.tsx
│       │   │   ├── _error.tsx
│       │   │   ├── converter/
│       │   │   │   ├── csv-to-json.tsx
│       │   │   │   ├── csv-to-xml.tsx
│       │   │   │   ├── csv-to-yaml.tsx
│       │   │   │   ├── json-to-csv.tsx
│       │   │   │   ├── json-to-xml.tsx
│       │   │   │   ├── json-to-yaml.tsx
│       │   │   │   ├── xml-to-csv.tsx
│       │   │   │   ├── xml-to-json.tsx
│       │   │   │   ├── xml-to-yaml.tsx
│       │   │   │   ├── yaml-to-csv.tsx
│       │   │   │   ├── yaml-to-json.tsx
│       │   │   │   └── yaml-to-xml.tsx
│       │   │   ├── docs.tsx
│       │   │   ├── editor.tsx
│       │   │   ├── index.tsx
│       │   │   ├── legal/
│       │   │   │   ├── privacy.tsx
│       │   │   │   └── terms.tsx
│       │   │   ├── tools/
│       │   │   │   └── json-schema.tsx
│       │   │   ├── type/
│       │   │   │   ├── csv-to-go.tsx
│       │   │   │   ├── csv-to-kotlin.tsx
│       │   │   │   ├── csv-to-rust.tsx
│       │   │   │   ├── csv-to-typescript.tsx
│       │   │   │   ├── json-to-go.tsx
│       │   │   │   ├── json-to-kotlin.tsx
│       │   │   │   ├── json-to-rust.tsx
│       │   │   │   ├── json-to-typescript.tsx
│       │   │   │   ├── xml-to-go.tsx
│       │   │   │   ├── xml-to-kotlin.tsx
│       │   │   │   ├── xml-to-rust.tsx
│       │   │   │   ├── xml-to-typescript.tsx
│       │   │   │   ├── yaml-to-go.tsx
│       │   │   │   ├── yaml-to-kotlin.tsx
│       │   │   │   ├── yaml-to-rust.tsx
│       │   │   │   └── yaml-to-typescript.tsx
│       │   │   └── widget.tsx
│       │   ├── store/
│       │   │   ├── useConfig.ts
│       │   │   ├── useFile.ts
│       │   │   ├── useJson.ts
│       │   │   └── useModal.ts
│       │   └── types/
│       │       └── styled.d.ts
│       └── tsconfig.json
├── package.json
├── packages/
│   └── jsoncrack-react/
│       ├── .gitignore
│       ├── .prettierrc
│       ├── LICENSE.md
│       ├── README.md
│       ├── eslint.config.mjs
│       ├── package.json
│       ├── src/
│       │   ├── JSONCrackComponent.tsx
│       │   ├── JSONCrackStyles.module.css
│       │   ├── components/
│       │   │   ├── Controls.module.css
│       │   │   ├── Controls.tsx
│       │   │   ├── CustomEdge.tsx
│       │   │   ├── CustomNode.tsx
│       │   │   ├── Node.module.css
│       │   │   ├── ObjectNode.tsx
│       │   │   ├── TextNode.tsx
│       │   │   ├── TextRenderer.module.css
│       │   │   ├── TextRenderer.tsx
│       │   │   └── nodeStyles.ts
│       │   ├── css-modules.d.ts
│       │   ├── index.ts
│       │   ├── parser.ts
│       │   ├── theme.ts
│       │   ├── types.ts
│       │   └── utils/
│       │       └── calculateNodeSize.ts
│       ├── tsconfig.build.json
│       ├── tsconfig.json
│       └── vite.config.ts
├── pnpm-workspace.yaml
└── turbo.json
Download .txt
SYMBOL INDEX (102 symbols across 54 files)

FILE: apps/vscode/ext-src/extension.ts
  function getPanelTitle (line 5) | function getPanelTitle(document?: vscode.TextDocument) {
  function activate (line 12) | function activate(context: vscode.ExtensionContext) {
  function createWebviewForSelectedText (line 27) | async function createWebviewForSelectedText(context: vscode.ExtensionCon...
  function createWebviewForActiveEditor (line 67) | async function createWebviewForActiveEditor(context: vscode.ExtensionCon...
  function createWebviewForContent (line 100) | function createWebviewForContent(context?: vscode.ExtensionContext, cont...
  function deactivate (line 113) | function deactivate() {}

FILE: apps/vscode/ext-src/webview.ts
  function createWebviewPanel (line 4) | function createWebviewPanel(context: vscode.ExtensionContext, title = "J...
  function getNonce (line 53) | function getNonce() {

FILE: apps/vscode/src/App.tsx
  function loadShiki (line 8) | async function loadShiki() {
  function getTheme (line 15) | function getTheme() {
  type Window (line 82) | interface Window {

FILE: apps/vscode/src/components/NodeModal.tsx
  type NodeModalProps (line 6) | interface NodeModalProps extends ModalProps {

FILE: apps/www/src/constants/graph.ts
  constant NODE_DIMENSIONS (line 1) | const NODE_DIMENSIONS = {
  constant SUPPORTED_LIMIT (line 6) | const SUPPORTED_LIMIT = +(process.env.NEXT_PUBLIC_NODE_LIMIT as string);

FILE: apps/www/src/constants/seo.ts
  constant SEO (line 3) | const SEO: DefaultSeoProps = {

FILE: apps/www/src/enums/file.enum.ts
  type FileFormat (line 1) | enum FileFormat {
  type TypeLanguage (line 15) | enum TypeLanguage {

FILE: apps/www/src/enums/viewMode.enum.ts
  type ViewMode (line 1) | enum ViewMode {

FILE: apps/www/src/features/Banner.tsx
  constant BANNER_HEIGHT (line 6) | const BANNER_HEIGHT =
  constant BANNER_LIST (line 9) | const BANNER_LIST = [

FILE: apps/www/src/features/editor/Toolbar/index.tsx
  function fullscreenBrowser (line 34) | function fullscreenBrowser() {

FILE: apps/www/src/features/editor/views/GraphView/CustomNode/ObjectNode.tsx
  type RowProps (line 8) | type RowProps = {
  function propsAreEqual (line 52) | function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {

FILE: apps/www/src/features/editor/views/GraphView/CustomNode/TextNode.tsx
  function propsAreEqual (line 43) | function propsAreEqual(prev: CustomNodeProps, next: CustomNodeProps) {

FILE: apps/www/src/features/editor/views/GraphView/CustomNode/TextRenderer.tsx
  function isColorFormat (line 48) | function isColorFormat(colorString: string) {

FILE: apps/www/src/features/editor/views/GraphView/CustomNode/index.tsx
  type CustomNodeProps (line 11) | interface CustomNodeProps {

FILE: apps/www/src/features/editor/views/GraphView/CustomNode/styles.tsx
  type TextColorFn (line 6) | type TextColorFn = {
  function getTextColor (line 12) | function getTextColor({ $value, $type, theme }: TextColorFn) {

FILE: apps/www/src/features/editor/views/GraphView/index.tsx
  type GraphProps (line 27) | interface GraphProps {

FILE: apps/www/src/features/editor/views/GraphView/lib/jsonParser.ts
  type Graph (line 10) | type Graph = {
  function traverse (line 24) | function traverse(node: Node, parentId?: string) {

FILE: apps/www/src/features/editor/views/GraphView/lib/utils/calculateNodeSize.ts
  type Text (line 3) | type Text = number | string | [string, string][];
  type Size (line 4) | type Size = { width: number; height: number };

FILE: apps/www/src/features/editor/views/GraphView/lib/utils/getOutgoers.ts
  type Outgoers (line 3) | type Outgoers = [NodeData[], string[]];

FILE: apps/www/src/features/editor/views/GraphView/stores/useGraph.ts
  type Graph (line 5) | interface Graph {
  type GraphActions (line 19) | interface GraphActions {

FILE: apps/www/src/features/editor/views/TreeView/Label.tsx
  type LabelProps (line 6) | interface LabelProps {
  function getLabelColor (line 11) | function getLabelColor({ $type, theme }: { $type?: string; theme: Defaul...

FILE: apps/www/src/features/editor/views/TreeView/Value.tsx
  type TextColorFn (line 6) | type TextColorFn = {
  function getValueColor (line 11) | function getValueColor({ $value, theme }: TextColorFn) {
  type ValueProps (line 21) | interface ValueProps {

FILE: apps/www/src/features/modals/DownloadModal/index.tsx
  type Extensions (line 18) | enum Extensions {
  function downloadURI (line 55) | function downloadURI(uri: string, name: string) {

FILE: apps/www/src/features/modals/TypeModal/index.tsx
  type Language (line 8) | enum Language {

FILE: apps/www/src/features/modals/modalTypes.ts
  type ModalName (line 9) | type ModalName = (typeof modals)[number];

FILE: apps/www/src/layout/ConverterLayout/PageLinks.tsx
  function groupCombinations (line 9) | function groupCombinations(array: string[]): Record<string, string[]> {

FILE: apps/www/src/layout/ConverterLayout/ToolPage.tsx
  type ToolPageProps (line 14) | interface ToolPageProps {

FILE: apps/www/src/layout/JSONCrackBrandLogo.tsx
  type LogoProps (line 32) | interface LogoProps {

FILE: apps/www/src/layout/Landing/Features.tsx
  type FeatureItem (line 19) | interface FeatureItem {

FILE: apps/www/src/layout/TypeLayout/PageLinks.tsx
  type MappedCombinations (line 7) | type MappedCombinations = {
  function mapLanguagesToProgramming (line 11) | function mapLanguagesToProgramming(

FILE: apps/www/src/layout/TypeLayout/TypegenWrapper.tsx
  type ConverterPagesProps (line 14) | interface ConverterPagesProps {

FILE: apps/www/src/lib/utils/helpers.ts
  function isIframe (line 1) | function isIframe() {

FILE: apps/www/src/lib/utils/json2go.js
  function jsonToGo (line 10) | function jsonToGo(json, typename, flatten = true, example = false, allOm...

FILE: apps/www/src/lib/utils/mantineColorScheme.ts
  type SmartColorSchemeManagerOptions (line 3) | interface SmartColorSchemeManagerOptions {
  function smartColorSchemeManager (line 17) | function smartColorSchemeManager({

FILE: apps/www/src/pages/_app.tsx
  function loadShiki (line 19) | async function loadShiki() {
  function JSONCrackApp (line 70) | function JSONCrackApp({ Component, pageProps }: AppProps) {

FILE: apps/www/src/pages/_document.tsx
  class MyDocument (line 6) | class MyDocument extends Document {
    method getInitialProps (line 7) | static async getInitialProps(ctx: DocumentContext): Promise<DocumentIn...
    method render (line 33) | render() {

FILE: apps/www/src/pages/widget.tsx
  type EmbedMessage (line 15) | interface EmbedMessage {

FILE: apps/www/src/store/useConfig.ts
  type ConfigActions (line 11) | interface ConfigActions {

FILE: apps/www/src/store/useFile.ts
  type SetContents (line 14) | type SetContents = {
  type Query (line 21) | type Query = string | string[] | undefined;
  type JsonActions (line 23) | interface JsonActions {
  type File (line 38) | type File = {
  type FileStates (line 59) | type FileStates = typeof initialStates;

FILE: apps/www/src/store/useJson.ts
  type JsonActions (line 3) | interface JsonActions {
  type JsonStates (line 14) | type JsonStates = typeof initialStates;

FILE: apps/www/src/store/useModal.ts
  type ModalActions (line 4) | interface ModalActions {
  type ModalState (line 8) | type ModalState = Record<ModalName, boolean>;

FILE: apps/www/src/types/styled.d.ts
  type CustomTheme (line 5) | type CustomTheme = typeof theme;
  type DefaultTheme (line 8) | interface DefaultTheme extends CustomTheme {}

FILE: packages/jsoncrack-react/src/JSONCrackComponent.tsx
  type JSONCrackRef (line 24) | interface JSONCrackRef {
  type JSONCrackProps (line 32) | interface JSONCrackProps {

FILE: packages/jsoncrack-react/src/components/Controls.tsx
  type ControlsProps (line 3) | interface ControlsProps {

FILE: packages/jsoncrack-react/src/components/CustomEdge.tsx
  type QueryRoot (line 7) | type QueryRoot = {
  type CustomEdgeProps (line 11) | type CustomEdgeProps = EdgeProps & {

FILE: packages/jsoncrack-react/src/components/CustomNode.tsx
  type CustomNodeProps (line 8) | type CustomNodeProps = NodeProps<NodeData> & {

FILE: packages/jsoncrack-react/src/components/ObjectNode.tsx
  type ObjectNodeProps (line 7) | type ObjectNodeProps = {
  type RowProps (line 13) | type RowProps = {
  constant ROW_HEIGHT (line 20) | const ROW_HEIGHT = 30;

FILE: packages/jsoncrack-react/src/components/TextNode.tsx
  type TextNodeProps (line 7) | type TextNodeProps = {

FILE: packages/jsoncrack-react/src/components/TextRenderer.tsx
  constant URL_PATTERN (line 4) | const URL_PATTERN =
  constant HEX_CODE_PATTERN (line 6) | const HEX_CODE_PATTERN = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
  constant RGB_PATTERN (line 7) | const RGB_PATTERN = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/;
  constant RGBA_PATTERN (line 8) | const RGBA_PATTERN = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(0|1...

FILE: packages/jsoncrack-react/src/components/nodeStyles.ts
  type TextColorOptions (line 1) | type TextColorOptions = {

FILE: packages/jsoncrack-react/src/parser.ts
  type ParseGraphResult (line 5) | interface ParseGraphResult extends GraphData {
  function traverse (line 26) | function traverse(node: Node, parentId?: string): string | undefined {

FILE: packages/jsoncrack-react/src/theme.ts
  type JSONCrackTheme (line 3) | interface JSONCrackTheme {

FILE: packages/jsoncrack-react/src/types.ts
  type NodeRow (line 3) | interface NodeRow {
  type NodeData (line 11) | interface NodeData {
  type EdgeData (line 21) | interface EdgeData {
  type GraphData (line 28) | interface GraphData {
  type LayoutDirection (line 33) | type LayoutDirection = "LEFT" | "RIGHT" | "DOWN" | "UP";
  type CanvasThemeMode (line 35) | type CanvasThemeMode = "light" | "dark";

FILE: packages/jsoncrack-react/src/utils/calculateNodeSize.ts
  constant NODE_DIMENSIONS (line 1) | const NODE_DIMENSIONS = {
  type Text (line 6) | type Text = number | string | [string, string][];
  type Size (line 7) | type Size = { width: number; height: number };
  constant CACHE_TTL_MS (line 9) | const CACHE_TTL_MS = 120_000;
Condensed preview — 203 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (391K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1426,
    "preview": "name: Bug report\ndescription: Create a report to help us improve\ntitle: \"[BUG]: <Context of the issue>\"\nlabels: bug\nbody"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 851,
    "preview": "name: Feature request\ndescription: Request a new feature\nlabels: [feature]\nbody:\n  - type: textarea\n    id: description\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 1036,
    "preview": "## Issue\nCloses #[ISSUE_NUMBER]\n\n## What Changed\nBrief description of what you changed and why.\n\nExample:\n- Added JSON v"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 2308,
    "preview": "name: Deploy Next.js site to Pages\n\non:\n  push:\n    branches: [\"main\"]\n\n  workflow_dispatch:\n\npermissions:\n  contents: r"
  },
  {
    "path": ".github/workflows/pull-request.yml",
    "chars": 1524,
    "preview": "name: Verify Pull Request\n\non:\n  pull_request:\n    branches: [ \"main\" ]\n\njobs:\n  cache-and-install:\n    runs-on: ubuntu-"
  },
  {
    "path": ".gitignore",
    "chars": 313,
    "preview": "# 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*\n"
  },
  {
    "path": ".npmrc",
    "chars": 19,
    "preview": "engine-strict=true\n"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 353,
    "preview": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Run VSCode Extension\",\n      \"type\": \"extensionHost\","
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 381,
    "preview": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"label\": \"build vscode extension\",\n      \"type\": \"shell\",\n      \"comman"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5487,
    "preview": "\n# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make particip"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3422,
    "preview": "# Contributing to JSON Crack\n\nThank you for wanting to contribute! This is a community-driven project, and we appreciate"
  },
  {
    "path": "LICENSE.md",
    "chars": 11341,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 5280,
    "preview": "<!-- PROJECT LOGO -->\n<p align=\"center\">\n  <a href=\"https://github.com/AykutSarac/jsoncrack.com\">\n   <img src=\"./apps/ww"
  },
  {
    "path": "apps/vscode/.gitignore",
    "chars": 27,
    "preview": "node_modules/\nbuild/\n*.vsix"
  },
  {
    "path": "apps/vscode/.prettierignore",
    "chars": 80,
    "preview": ".github\n.next\nnode_modules/\nout\npublic\n*-lock.json\ntsconfig.json\nbuild\njsoncrack"
  },
  {
    "path": "apps/vscode/.prettierrc",
    "chars": 444,
    "preview": "{\n  \"trailingComma\": \"es5\",\n  \"singleQuote\": false,\n  \"semi\": true,\n  \"printWidth\": 100,\n  \"arrowParens\": \"avoid\",\n  \"im"
  },
  {
    "path": "apps/vscode/.vscodeignore",
    "chars": 210,
    "preview": ".vscode/**\n.vscode-test/**\n.github/**\n.turbo/**\nnode_modules/**\nsrc/**\next-src/**\n.gitignore\n.prettierignore\n.prettierrc"
  },
  {
    "path": "apps/vscode/LICENSE.md",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2025 Aykut Saraç\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "apps/vscode/README.md",
    "chars": 2072,
    "preview": "  <img src=\"https://github.com/AykutSarac/jsoncrack-vscode/assets/47941171/23b26537-7c4a-4029-af78-456dea0d0b04\" width=\""
  },
  {
    "path": "apps/vscode/esbuild.config.mjs",
    "chars": 599,
    "preview": "import * as esbuild from \"esbuild\";\n\nconst isWatch = process.argv.includes(\"--watch\");\nconst isProduction = process.argv"
  },
  {
    "path": "apps/vscode/eslint.config.mjs",
    "chars": 1593,
    "preview": "import eslint from \"@eslint/js\";\nimport { defineConfig, globalIgnores } from \"eslint/config\";\nimport eslintConfigPrettie"
  },
  {
    "path": "apps/vscode/ext-src/extension.ts",
    "chars": 3284,
    "preview": "import * as path from \"path\";\nimport * as vscode from \"vscode\";\nimport { createWebviewPanel } from \"./webview\";\n\nfunctio"
  },
  {
    "path": "apps/vscode/ext-src/webview.ts",
    "chars": 1985,
    "preview": "import * as path from \"path\";\nimport * as vscode from \"vscode\";\n\nexport function createWebviewPanel(context: vscode.Exte"
  },
  {
    "path": "apps/vscode/index.html",
    "chars": 356,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-w"
  },
  {
    "path": "apps/vscode/package.json",
    "chars": 3502,
    "preview": "{\n  \"name\": \"vscode\",\n  \"version\": \"3.0.0\",\n  \"displayName\": \"JSON Crack\",\n  \"description\": \"Seamlessly visualize your J"
  },
  {
    "path": "apps/vscode/src/App.tsx",
    "chars": 2509,
    "preview": "import { useCallback, useEffect, useState } from \"react\";\nimport { Anchor, Box, MantineProvider, Text } from \"@mantine/c"
  },
  {
    "path": "apps/vscode/src/components/NodeModal.tsx",
    "chars": 1999,
    "preview": "import type { ModalProps } from \"@mantine/core\";\nimport { Modal, Stack, Text, ScrollArea } from \"@mantine/core\";\nimport "
  },
  {
    "path": "apps/vscode/src/env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "apps/vscode/src/global.css",
    "chars": 204,
    "preview": "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*::af"
  },
  {
    "path": "apps/vscode/src/index.tsx",
    "chars": 336,
    "preview": "import \"@mantine/core/styles.css\";\nimport \"@mantine/code-highlight/styles.css\";\nimport \"jsoncrack-react/style.css\";\nimpo"
  },
  {
    "path": "apps/vscode/tsconfig.json",
    "chars": 430,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"module\": \"esnext\",\n "
  },
  {
    "path": "apps/vscode/vite.config.ts",
    "chars": 706,
    "preview": "import react from \"@vitejs/plugin-react\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport"
  },
  {
    "path": "apps/www/.dockerignore",
    "chars": 72,
    "preview": "Dockerfile\n.dockerignore\nnode_modules\nnpm-debug.log\nREADME.md\n.next\n.git"
  },
  {
    "path": "apps/www/.gitignore",
    "chars": 413,
    "preview": "# App dependencies\nnode_modules/\n\n# Next.js\n.next/\nout/\n\n# Turborepo task cache\n.turbo/\n\n# Test/build artifacts\ncoverage"
  },
  {
    "path": "apps/www/.prettierignore",
    "chars": 64,
    "preview": ".github\n.next\nnode_modules/\nout\npublic\n*-lock.json\ntsconfig.json"
  },
  {
    "path": "apps/www/.prettierrc",
    "chars": 444,
    "preview": "{\n  \"trailingComma\": \"es5\",\n  \"singleQuote\": false,\n  \"semi\": true,\n  \"printWidth\": 100,\n  \"arrowParens\": \"avoid\",\n  \"im"
  },
  {
    "path": "apps/www/Dockerfile",
    "chars": 586,
    "preview": "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\nWORK"
  },
  {
    "path": "apps/www/LICENSE.md",
    "chars": 11341,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "apps/www/docker-compose.yml",
    "chars": 207,
    "preview": "services:\n  jsoncrack:\n    image: jsoncrack\n    container_name: jsoncrack\n    build: \n      context: .\n      dockerfile:"
  },
  {
    "path": "apps/www/eslint.config.mjs",
    "chars": 1749,
    "preview": "import eslint from \"@eslint/js\";\nimport nextPlugin from \"@next/eslint-plugin-next\";\nimport eslintConfigPrettier from \"es"
  },
  {
    "path": "apps/www/next-env.d.ts",
    "chars": 253,
    "preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/dev/types/routes.d.ts\";"
  },
  {
    "path": "apps/www/next-sitemap.config.js",
    "chars": 173,
    "preview": "/** @type {import('next-sitemap').IConfig} */\nmodule.exports = {\n  siteUrl: \"https://jsoncrack.com\",\n  exclude: [\"/widge"
  },
  {
    "path": "apps/www/next.config.js",
    "chars": 975,
    "preview": "const withBundleAnalyzer = require(\"@next/bundle-analyzer\")({\n  enabled: process.env.ANALYZE === \"true\",\n});\n\n/**\n * @ty"
  },
  {
    "path": "apps/www/nginx.conf",
    "chars": 280,
    "preview": "server {\n    listen 8080;\n    root  /app;\n    include /etc/nginx/mime.types;\n\n    location /editor {\n        try_files $"
  },
  {
    "path": "apps/www/package.json",
    "chars": 2149,
    "preview": "{\n  \"name\": \"www\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"license\": \"Apache-2.0\",\n  \"scripts\": {\n    \"dev\": \"next d"
  },
  {
    "path": "apps/www/public/.nojekyll",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "apps/www/public/CNAME",
    "chars": 13,
    "preview": "jsoncrack.com"
  },
  {
    "path": "apps/www/public/manifest.json",
    "chars": 612,
    "preview": "{\n  \"name\": \"JSON Crack\",\n  \"short_name\": \"JSON Crack\",\n  \"description\": \"JSON Crack Editor is a tool for visualizing in"
  },
  {
    "path": "apps/www/public/robots.txt",
    "chars": 68,
    "preview": "User-agent: *\n\nAllow: /\n\nSitemap: https://jsoncrack.com/sitemap.xml\n"
  },
  {
    "path": "apps/www/public/sitemap-0.xml",
    "chars": 4356,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:news=\"http://ww"
  },
  {
    "path": "apps/www/public/sitemap.xml",
    "chars": 187,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n<sitemap><loc>"
  },
  {
    "path": "apps/www/shims/empty.ts",
    "chars": 11,
    "preview": "export {};\n"
  },
  {
    "path": "apps/www/src/constants/globalStyle.ts",
    "chars": 782,
    "preview": "import { createGlobalStyle } from \"styled-components\";\n\nconst GlobalStyle = createGlobalStyle`\n  html, body {\n    backgr"
  },
  {
    "path": "apps/www/src/constants/graph.ts",
    "chars": 214,
    "preview": "export const NODE_DIMENSIONS = {\n  ROW_HEIGHT: 30, // Regular row height\n  PARENT_HEIGHT: 36, // Height for parent nodes"
  },
  {
    "path": "apps/www/src/constants/seo.ts",
    "chars": 831,
    "preview": "import type { DefaultSeoProps } from \"next-seo/pages\";\n\nexport const SEO: DefaultSeoProps = {\n  title: \"JSON Crack | Onl"
  },
  {
    "path": "apps/www/src/constants/theme.ts",
    "chars": 2696,
    "preview": "const fixedColors = {\n  CRIMSON: \"#DC143C\",\n  BLURPLE: \"#5865F2\",\n  PURPLE: \"#9036AF\",\n  FULL_WHITE: \"#FFFFFF\",\n  BLACK:"
  },
  {
    "path": "apps/www/src/data/example.json",
    "chars": 771,
    "preview": "{\n  \"fruits\": [\n    {\n      \"name\": \"Apple\",\n      \"color\": \"#FF0000\",\n      \"details\": {\n        \"type\": \"Pome\",\n      "
  },
  {
    "path": "apps/www/src/data/faq.json",
    "chars": 2422,
    "preview": "[\n  {\n    \"title\": \"What is JSON Crack and what does it do?\",\n    \"content\": \"JSON Crack is an online JSON viewer tool d"
  },
  {
    "path": "apps/www/src/data/privacy.json",
    "chars": 3197,
    "preview": "{\n  \"Introduction\": [\n    \"Welcome to JSON Crack.\",\n    \"JSON Crack (“us”, “we”, or “our”) operates https://jsoncrack.co"
  },
  {
    "path": "apps/www/src/data/terms.json",
    "chars": 9503,
    "preview": "{\n  \"Introduction\": [\n    \"Subject to these Terms of Service (“Terms”, “Terms of Service”), jsoncrack.com ('JSON Crack',"
  },
  {
    "path": "apps/www/src/enums/file.enum.ts",
    "chars": 1050,
    "preview": "export enum FileFormat {\n  \"JSON\" = \"json\",\n  \"YAML\" = \"yaml\",\n  \"XML\" = \"xml\",\n  \"CSV\" = \"csv\",\n}\n\nexport const formats"
  },
  {
    "path": "apps/www/src/enums/viewMode.enum.ts",
    "chars": 61,
    "preview": "export enum ViewMode {\n  Graph = \"graph\",\n  Tree = \"tree\",\n}\n"
  },
  {
    "path": "apps/www/src/features/Banner.tsx",
    "chars": 2834,
    "preview": "import React, { useEffect, useState } from \"react\";\nimport { Anchor, Flex, Button, ActionIcon } from \"@mantine/core\";\nim"
  },
  {
    "path": "apps/www/src/features/editor/BottomBar.tsx",
    "chars": 5030,
    "preview": "import React from \"react\";\nimport { Flex, Menu, Popover, Text } from \"@mantine/core\";\nimport styled from \"styled-compone"
  },
  {
    "path": "apps/www/src/features/editor/ExternalMode.tsx",
    "chars": 5944,
    "preview": "import React from \"react\";\nimport { Accordion, Anchor, Code, Flex, FocusTrap, Group, Modal, Text } from \"@mantine/core\";"
  },
  {
    "path": "apps/www/src/features/editor/FullscreenDropzone.tsx",
    "chars": 1903,
    "preview": "import React from \"react\";\nimport { Group, Text } from \"@mantine/core\";\nimport { Dropzone } from \"@mantine/dropzone\";\nim"
  },
  {
    "path": "apps/www/src/features/editor/LiveEditor.tsx",
    "chars": 1092,
    "preview": "import React from \"react\";\nimport { useSessionStorage } from \"@mantine/hooks\";\nimport styled from \"styled-components\";\ni"
  },
  {
    "path": "apps/www/src/features/editor/TextEditor.tsx",
    "chars": 3188,
    "preview": "import React, { useCallback } from \"react\";\nimport { LoadingOverlay } from \"@mantine/core\";\nimport styled from \"styled-c"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/FileMenu.tsx",
    "chars": 1304,
    "preview": "import React from \"react\";\nimport { Flex, Menu } from \"@mantine/core\";\nimport { event as gaEvent } from \"nextjs-google-a"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/SearchInput.tsx",
    "chars": 1210,
    "preview": "import React from \"react\";\nimport { Flex, Text, TextInput } from \"@mantine/core\";\nimport { getHotkeyHandler } from \"@man"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/ThemeToggle.tsx",
    "chars": 524,
    "preview": "import { FaMoon, FaSun } from \"react-icons/fa6\";\nimport useConfig from \"../../../store/useConfig\";\nimport { StyledToolEl"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/ToolsMenu.tsx",
    "chars": 1826,
    "preview": "import React from \"react\";\nimport { Menu, Flex } from \"@mantine/core\";\nimport { event as gaEvent } from \"nextjs-google-a"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/ViewMenu.tsx",
    "chars": 1271,
    "preview": "import { Menu, Flex, SegmentedControl } from \"@mantine/core\";\nimport { useSessionStorage } from \"@mantine/hooks\";\nimport"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/index.tsx",
    "chars": 2205,
    "preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Flex, Group } from \"@mantine/core\";\nimport styled from"
  },
  {
    "path": "apps/www/src/features/editor/Toolbar/styles.ts",
    "chars": 679,
    "preview": "import styled from \"styled-components\";\n\nexport const StyledToolElement = styled.button<{ $hide?: boolean; $highlight?: "
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomEdge/index.tsx",
    "chars": 1253,
    "preview": "import React from \"react\";\nimport { useComputedColorScheme } from \"@mantine/core\";\nimport type { EdgeProps } from \"reafl"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/ObjectNode.tsx",
    "chars": 1610,
    "preview": "import React from \"react\";\nimport type { NodeData } from \"jsoncrack-react\";\nimport type { CustomNodeProps } from \".\";\nim"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/TextNode.tsx",
    "chars": 1302,
    "preview": "import React from \"react\";\nimport styled from \"styled-components\";\nimport type { CustomNodeProps } from \".\";\nimport { Te"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/TextRenderer.tsx",
    "chars": 1741,
    "preview": "import React from \"react\";\nimport { ColorSwatch } from \"@mantine/core\";\nimport styled from \"styled-components\";\n\nconst S"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/index.tsx",
    "chars": 1835,
    "preview": "import React from \"react\";\nimport { useComputedColorScheme } from \"@mantine/core\";\nimport type { NodeData } from \"jsoncr"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/CustomNode/styles.tsx",
    "chars": 2722,
    "preview": "import type { DefaultTheme } from \"styled-components\";\nimport styled from \"styled-components\";\nimport { LinkItUrl } from"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/NotSupported.tsx",
    "chars": 2781,
    "preview": "import React from \"react\";\nimport { Anchor, Button, Image, Overlay, Stack, Text } from \"@mantine/core\";\nimport styled, {"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/OptionsMenu.tsx",
    "chars": 4740,
    "preview": "import React from \"react\";\nimport { ActionIcon, Flex, Menu, Text } from \"@mantine/core\";\nimport { useHotkeys } from \"@ma"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/SecureInfo.tsx",
    "chars": 653,
    "preview": "import React from \"react\";\nimport { ThemeIcon, Tooltip } from \"@mantine/core\";\nimport { LuShieldCheck } from \"react-icon"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/ZoomControl.tsx",
    "chars": 2829,
    "preview": "import React from \"react\";\nimport { ActionIcon, Flex, Tooltip, Text } from \"@mantine/core\";\nimport { useHotkeys } from \""
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/index.tsx",
    "chars": 3253,
    "preview": "import React from \"react\";\nimport { Box } from \"@mantine/core\";\nimport styled from \"styled-components\";\nimport { JSONCra"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/lib/jsonParser.ts",
    "chars": 5583,
    "preview": "/**\n * Copyright (c) JSON Crack\n * This source code is licensed under the Apache 2.0 license found in the\n * LICENSE fil"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/lib/utils/calculateNodeSize.ts",
    "chars": 1884,
    "preview": "import { NODE_DIMENSIONS } from \"../../../../../../constants/graph\";\n\ntype Text = number | string | [string, string][];\n"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/lib/utils/getChildrenEdges.ts",
    "chars": 315,
    "preview": "import type { EdgeData, NodeData } from \"jsoncrack-react\";\n\nexport const getChildrenEdges = (nodes: NodeData[], edges: E"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/lib/utils/getOutgoers.ts",
    "chars": 982,
    "preview": "import type { EdgeData, NodeData } from \"jsoncrack-react\";\n\ntype Outgoers = [NodeData[], string[]];\n\nexport const getOut"
  },
  {
    "path": "apps/www/src/features/editor/views/GraphView/stores/useGraph.ts",
    "chars": 1938,
    "preview": "import type { LayoutDirection, NodeData } from \"jsoncrack-react\";\nimport type { ViewPort } from \"react-zoomable-ui\";\nimp"
  },
  {
    "path": "apps/www/src/features/editor/views/TreeView/Label.tsx",
    "chars": 859,
    "preview": "import React from \"react\";\nimport type { DefaultTheme } from \"styled-components\";\nimport { styled } from \"styled-compone"
  },
  {
    "path": "apps/www/src/features/editor/views/TreeView/Value.tsx",
    "chars": 1052,
    "preview": "import React from \"react\";\nimport type { DefaultTheme } from \"styled-components\";\nimport { useTheme } from \"styled-compo"
  },
  {
    "path": "apps/www/src/features/editor/views/TreeView/index.tsx",
    "chars": 832,
    "preview": "import React from \"react\";\nimport { useTheme } from \"styled-components\";\nimport { JSONTree } from \"react-json-tree\";\nimp"
  },
  {
    "path": "apps/www/src/features/modals/DownloadModal/index.tsx",
    "chars": 4989,
    "preview": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport {\n  ColorPicker,\n  TextInput,\n  Segme"
  },
  {
    "path": "apps/www/src/features/modals/ImportModal/index.tsx",
    "chars": 2878,
    "preview": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Modal, Group, Button, TextInput, St"
  },
  {
    "path": "apps/www/src/features/modals/JPathModal/index.tsx",
    "chars": 2142,
    "preview": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Stack, Modal, Button, Text, Anchor,"
  },
  {
    "path": "apps/www/src/features/modals/JQModal/index.tsx",
    "chars": 1336,
    "preview": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Stack, Modal, Button, Text, Anchor,"
  },
  {
    "path": "apps/www/src/features/modals/ModalController.tsx",
    "chars": 624,
    "preview": "import React from \"react\";\nimport * as ModalComponents from \".\";\nimport { useModal } from \"../../store/useModal\";\nimport"
  },
  {
    "path": "apps/www/src/features/modals/NodeModal/index.tsx",
    "chars": 2312,
    "preview": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Modal, Stack, Text, ScrollArea, Fle"
  },
  {
    "path": "apps/www/src/features/modals/SchemaModal/index.tsx",
    "chars": 2834,
    "preview": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Stack, Modal, Button, Text, Anchor,"
  },
  {
    "path": "apps/www/src/features/modals/TypeModal/index.tsx",
    "chars": 2973,
    "preview": "import React from \"react\";\nimport type { ModalProps } from \"@mantine/core\";\nimport { Stack, Modal, Select, ScrollArea } "
  },
  {
    "path": "apps/www/src/features/modals/index.ts",
    "chars": 301,
    "preview": "export { DownloadModal } from \"./DownloadModal\";\nexport { ImportModal } from \"./ImportModal\";\nexport { NodeModal } from "
  },
  {
    "path": "apps/www/src/features/modals/modalTypes.ts",
    "chars": 274,
    "preview": "import * as ModalComponents from \".\";\n\n// Define the modals array separate from the component logic\nexport const modals "
  },
  {
    "path": "apps/www/src/hooks/useFocusNode.ts",
    "chars": 1588,
    "preview": "import React from \"react\";\nimport { useDebouncedValue } from \"@mantine/hooks\";\nimport { event as gaEvent } from \"nextjs-"
  },
  {
    "path": "apps/www/src/hooks/useJsonQuery.ts",
    "chars": 1023,
    "preview": "import toast from \"react-hot-toast\";\nimport useFile from \"../store/useFile\";\nimport useJson from \"../store/useJson\";\n\nco"
  },
  {
    "path": "apps/www/src/layout/ConverterLayout/PageLinks.tsx",
    "chars": 2029,
    "preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Anchor, Button, Flex, List, SimpleGrid, Stack } from \""
  },
  {
    "path": "apps/www/src/layout/ConverterLayout/ToolPage.tsx",
    "chars": 4130,
    "preview": "import React, { useEffect, useRef } from \"react\";\nimport Head from \"next/head\";\nimport { Box, Container, Flex, Paper, Te"
  },
  {
    "path": "apps/www/src/layout/ConverterLayout/options.ts",
    "chars": 303,
    "preview": "import type { EditorProps } from \"@monaco-editor/react\";\n\nexport const editorOptions: EditorProps[\"options\"] = {\n  forma"
  },
  {
    "path": "apps/www/src/layout/JSONCrackBrandLogo.tsx",
    "chars": 1777,
    "preview": "import React from \"react\";\nimport localFont from \"next/font/local\";\nimport Link from \"next/link\";\nimport { Image } from "
  },
  {
    "path": "apps/www/src/layout/Landing/FAQ.tsx",
    "chars": 1276,
    "preview": "import React from \"react\";\nimport { Container, Title, Accordion } from \"@mantine/core\";\nimport Questions from \"../../dat"
  },
  {
    "path": "apps/www/src/layout/Landing/Features.tsx",
    "chars": 4262,
    "preview": "import React from \"react\";\nimport {\n  Container,\n  Flex,\n  Title,\n  Text,\n  Paper,\n  Center,\n  Badge,\n  ThemeIcon,\n  Sim"
  },
  {
    "path": "apps/www/src/layout/Landing/HeroPreview.tsx",
    "chars": 575,
    "preview": "import React from \"react\";\nimport { Container, Image } from \"@mantine/core\";\n\nexport const HeroPreview = () => {\n  retur"
  },
  {
    "path": "apps/www/src/layout/Landing/HeroSection.tsx",
    "chars": 4111,
    "preview": "import React from \"react\";\nimport { Oxygen } from \"next/font/google\";\nimport Link from \"next/link\";\nimport { Stack, Flex"
  },
  {
    "path": "apps/www/src/layout/Landing/Section1.tsx",
    "chars": 3852,
    "preview": "import React from \"react\";\nimport { Container, Image, SimpleGrid, Stack, Text, Title } from \"@mantine/core\";\nimport styl"
  },
  {
    "path": "apps/www/src/layout/Landing/Section2.tsx",
    "chars": 4451,
    "preview": "import React from \"react\";\nimport {\n  Button,\n  Container,\n  Flex,\n  Image,\n  JsonInput,\n  List,\n  SimpleGrid,\n  Stack,\n"
  },
  {
    "path": "apps/www/src/layout/Landing/Section3.tsx",
    "chars": 2921,
    "preview": "import React from \"react\";\nimport {\n  Button,\n  Container,\n  Flex,\n  Image,\n  List,\n  SimpleGrid,\n  Stack,\n  Text,\n  Tit"
  },
  {
    "path": "apps/www/src/layout/PageLayout/Footer.tsx",
    "chars": 4061,
    "preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Anchor, Container, Divider, Flex, Stack, Text, ThemeIc"
  },
  {
    "path": "apps/www/src/layout/PageLayout/Navbar.tsx",
    "chars": 4977,
    "preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Button, Menu, type MenuItemProps, Text, Stack } from \""
  },
  {
    "path": "apps/www/src/layout/PageLayout/index.tsx",
    "chars": 842,
    "preview": "import React from \"react\";\nimport { Inter } from \"next/font/google\";\nimport styled, { ThemeProvider } from \"styled-compo"
  },
  {
    "path": "apps/www/src/layout/TypeLayout/PageLinks.tsx",
    "chars": 2395,
    "preview": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Anchor, Button, Flex, List, SimpleGrid, Stack } from \""
  },
  {
    "path": "apps/www/src/layout/TypeLayout/TypegenWrapper.tsx",
    "chars": 3448,
    "preview": "import React, { useEffect, useRef } from \"react\";\nimport Head from \"next/head\";\nimport { Box, Container, Flex, Paper, Ti"
  },
  {
    "path": "apps/www/src/lib/utils/generateType.ts",
    "chars": 784,
    "preview": "import { type FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { contentToJson } from \"./jsonAdapter\";\n\ne"
  },
  {
    "path": "apps/www/src/lib/utils/helpers.ts",
    "chars": 111,
    "preview": "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",
    "chars": 11521,
    "preview": "/*\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 d"
  },
  {
    "path": "apps/www/src/lib/utils/jsonAdapter.ts",
    "chars": 2490,
    "preview": "import type { ParseError } from \"jsonc-parser\";\nimport { FileFormat } from \"../../enums/file.enum\";\n\nexport const conten"
  },
  {
    "path": "apps/www/src/lib/utils/mantineColorScheme.ts",
    "chars": 2507,
    "preview": "import { type MantineColorScheme, type MantineColorSchemeManager } from \"@mantine/core\";\n\nexport interface SmartColorSch"
  },
  {
    "path": "apps/www/src/lib/utils/search.ts",
    "chars": 703,
    "preview": "export const searchQuery = (param: string) => {\n  return document.querySelectorAll(param);\n};\n\nexport const cleanupHighl"
  },
  {
    "path": "apps/www/src/pages/404.tsx",
    "chars": 1093,
    "preview": "import React from \"react\";\nimport Head from \"next/head\";\nimport Link from \"next/link\";\nimport { Button, Stack, Text, Tit"
  },
  {
    "path": "apps/www/src/pages/_app.tsx",
    "chars": 3581,
    "preview": "import type { AppProps } from \"next/app\";\nimport Head from \"next/head\";\nimport { useRouter } from \"next/router\";\nimport "
  },
  {
    "path": "apps/www/src/pages/_document.tsx",
    "chars": 1157,
    "preview": "import type { DocumentContext, DocumentInitialProps } from \"next/document\";\nimport Document, { Html, Head, Main, NextScr"
  },
  {
    "path": "apps/www/src/pages/_error.tsx",
    "chars": 1155,
    "preview": "import React from \"react\";\nimport Head from \"next/head\";\nimport { useRouter } from \"next/router\";\nimport { Button, Stack"
  },
  {
    "path": "apps/www/src/pages/converter/csv-to-json.tsx",
    "chars": 258,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/csv-to-xml.tsx",
    "chars": 257,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/csv-to-yaml.tsx",
    "chars": 258,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/json-to-csv.tsx",
    "chars": 258,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/json-to-xml.tsx",
    "chars": 258,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/json-to-yaml.tsx",
    "chars": 259,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/xml-to-csv.tsx",
    "chars": 257,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/xml-to-json.tsx",
    "chars": 258,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/xml-to-yaml.tsx",
    "chars": 258,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/yaml-to-csv.tsx",
    "chars": 258,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/yaml-to-json.tsx",
    "chars": 259,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/converter/yaml-to-xml.tsx",
    "chars": 258,
    "preview": "import React from \"react\";\nimport { FileFormat } from \"../../enums/file.enum\";\nimport { ToolPage } from \"../../layout/Co"
  },
  {
    "path": "apps/www/src/pages/docs.tsx",
    "chars": 5680,
    "preview": "import React from \"react\";\nimport Head from \"next/head\";\nimport { Group, Paper, Stack, Text, Title } from \"@mantine/core"
  },
  {
    "path": "apps/www/src/pages/editor.tsx",
    "chars": 3977,
    "preview": "import { useEffect } from \"react\";\nimport dynamic from \"next/dynamic\";\nimport Head from \"next/head\";\nimport { useRouter "
  },
  {
    "path": "apps/www/src/pages/index.tsx",
    "chars": 1433,
    "preview": "import React from \"react\";\nimport type { InferGetStaticPropsType, GetStaticProps } from \"next\";\nimport Head from \"next/h"
  },
  {
    "path": "apps/www/src/pages/legal/privacy.tsx",
    "chars": 1473,
    "preview": "import React from \"react\";\nimport Head from \"next/head\";\nimport { Box, Container, Paper, Stack, Text, Title } from \"@man"
  },
  {
    "path": "apps/www/src/pages/legal/terms.tsx",
    "chars": 1465,
    "preview": "import React from \"react\";\nimport Head from \"next/head\";\nimport { Box, Container, Paper, Stack, Text, Title } from \"@man"
  },
  {
    "path": "apps/www/src/pages/tools/json-schema.tsx",
    "chars": 4433,
    "preview": "import React from \"react\";\nimport Head from \"next/head\";\nimport { Box, Button, Container, Flex, Paper, Title, Text } fro"
  },
  {
    "path": "apps/www/src/pages/type/csv-to-go.tsx",
    "chars": 293,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/csv-to-kotlin.tsx",
    "chars": 297,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/csv-to-rust.tsx",
    "chars": 295,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/csv-to-typescript.tsx",
    "chars": 301,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/json-to-go.tsx",
    "chars": 294,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/json-to-kotlin.tsx",
    "chars": 298,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/json-to-rust.tsx",
    "chars": 296,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/json-to-typescript.tsx",
    "chars": 302,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/xml-to-go.tsx",
    "chars": 293,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/xml-to-kotlin.tsx",
    "chars": 297,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/xml-to-rust.tsx",
    "chars": 295,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/xml-to-typescript.tsx",
    "chars": 301,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/yaml-to-go.tsx",
    "chars": 294,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/yaml-to-kotlin.tsx",
    "chars": 298,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/yaml-to-rust.tsx",
    "chars": 296,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/type/yaml-to-typescript.tsx",
    "chars": 302,
    "preview": "import React from \"react\";\nimport { FileFormat, TypeLanguage } from \"../../enums/file.enum\";\nimport { TypegenWrapper } f"
  },
  {
    "path": "apps/www/src/pages/widget.tsx",
    "chars": 2793,
    "preview": "import React from \"react\";\nimport dynamic from \"next/dynamic\";\nimport Head from \"next/head\";\nimport { useRouter } from \""
  },
  {
    "path": "apps/www/src/store/useConfig.ts",
    "chars": 894,
    "preview": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\n\nconst initialStates = {\n  darkmodeEnabl"
  },
  {
    "path": "apps/www/src/store/useFile.ts",
    "chars": 4998,
    "preview": "import debounce from \"lodash.debounce\";\nimport { event as gaEvent } from \"nextjs-google-analytics\";\nimport { toast } fro"
  },
  {
    "path": "apps/www/src/store/useJson.ts",
    "chars": 519,
    "preview": "import { create } from \"zustand\";\n\ninterface JsonActions {\n  setJson: (json: string) => void;\n  getJson: () => string;\n "
  },
  {
    "path": "apps/www/src/store/useModal.ts",
    "chars": 558,
    "preview": "import { createWithEqualityFn as create } from \"zustand/traditional\";\nimport { type ModalName, modals } from \"../feature"
  },
  {
    "path": "apps/www/src/types/styled.d.ts",
    "chars": 263,
    "preview": "/* eslint-disable @typescript-eslint/no-empty-object-type */\nimport \"styled-components\";\nimport type theme from \"../cons"
  },
  {
    "path": "apps/www/tsconfig.json",
    "chars": 609,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES6\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    "
  },
  {
    "path": "package.json",
    "chars": 1101,
    "preview": "{\n  \"name\": \"jsoncrack-monorepo\",\n  \"private\": true,\n  \"license\": \"Apache-2.0\",\n  \"homepage\": \"https://jsoncrack.com\",\n "
  },
  {
    "path": "packages/jsoncrack-react/.gitignore",
    "chars": 51,
    "preview": "node_modules\ndist/\n.turbo/\ncoverage/\n*.tsbuildinfo\n"
  },
  {
    "path": "packages/jsoncrack-react/.prettierrc",
    "chars": 343,
    "preview": "{\n  \"trailingComma\": \"es5\",\n  \"singleQuote\": false,\n  \"semi\": true,\n  \"printWidth\": 100,\n  \"arrowParens\": \"avoid\",\n  \"im"
  },
  {
    "path": "packages/jsoncrack-react/LICENSE.md",
    "chars": 11341,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "packages/jsoncrack-react/README.md",
    "chars": 4166,
    "preview": "# jsoncrack-react\n\nReusable JSON graph canvas component from [JSON Crack](https://jsoncrack.com) — visualize JSON as int"
  },
  {
    "path": "packages/jsoncrack-react/eslint.config.mjs",
    "chars": 1445,
    "preview": "import eslint from \"@eslint/js\";\nimport eslintConfigPrettier from \"eslint-config-prettier/flat\";\nimport eslintPluginPret"
  },
  {
    "path": "packages/jsoncrack-react/package.json",
    "chars": 1757,
    "preview": "{\n  \"name\": \"jsoncrack-react\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Reusable JSON Crack canvas as a React component\","
  },
  {
    "path": "packages/jsoncrack-react/src/JSONCrackComponent.tsx",
    "chars": 12287,
    "preview": "\"use client\";\n\nimport React from \"react\";\nimport type { ViewPort } from \"react-zoomable-ui\";\nimport { Space } from \"reac"
  },
  {
    "path": "packages/jsoncrack-react/src/JSONCrackStyles.module.css",
    "chars": 1760,
    "preview": ".canvasWrapper {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  background-color: var(--bg-color);\n}\n\n.showGrid "
  },
  {
    "path": "packages/jsoncrack-react/src/components/Controls.module.css",
    "chars": 555,
    "preview": ".controls {\n  position: absolute;\n  bottom: 10px;\n  left: 10px;\n  z-index: 100;\n  display: inline-flex;\n  align-items: c"
  },
  {
    "path": "packages/jsoncrack-react/src/components/Controls.tsx",
    "chars": 871,
    "preview": "import styles from \"./Controls.module.css\";\n\ninterface ControlsProps {\n  onFocusRoot: () => void;\n  onCenterView: () => "
  },
  {
    "path": "packages/jsoncrack-react/src/components/CustomEdge.tsx",
    "chars": 1915,
    "preview": "import React from \"react\";\nimport type { ViewPort } from \"react-zoomable-ui\";\nimport type { EdgeProps } from \"reaflow\";\n"
  },
  {
    "path": "packages/jsoncrack-react/src/components/CustomNode.tsx",
    "chars": 1371,
    "preview": "import React from \"react\";\nimport type { NodeProps } from \"reaflow\";\nimport { Node } from \"reaflow\";\nimport type { NodeD"
  },
  {
    "path": "packages/jsoncrack-react/src/components/Node.module.css",
    "chars": 1194,
    "preview": ".foreignObject {\n  text-align: center;\n  color: var(--node-text);\n  font-family:\n    ui-monospace, SFMono-Regular, Menlo"
  },
  {
    "path": "packages/jsoncrack-react/src/components/ObjectNode.tsx",
    "chars": 2847,
    "preview": "import React from \"react\";\nimport type { NodeData } from \"../types\";\nimport styles from \"./Node.module.css\";\nimport { Te"
  },
  {
    "path": "packages/jsoncrack-react/src/components/TextNode.tsx",
    "chars": 1210,
    "preview": "import React from \"react\";\nimport type { NodeData } from \"../types\";\nimport styles from \"./Node.module.css\";\nimport { Te"
  },
  {
    "path": "packages/jsoncrack-react/src/components/TextRenderer.module.css",
    "chars": 316,
    "preview": ".row {\n  display: inline-flex;\n  align-items: center;\n  overflow: hidden;\n  gap: 4px;\n  vertical-align: middle;\n}\n\n.colo"
  },
  {
    "path": "packages/jsoncrack-react/src/components/TextRenderer.tsx",
    "chars": 1542,
    "preview": "import React from \"react\";\nimport styles from \"./TextRenderer.module.css\";\n\nconst URL_PATTERN =\n  /^(http:\\/\\/www\\.|http"
  },
  {
    "path": "packages/jsoncrack-react/src/components/nodeStyles.ts",
    "chars": 458,
    "preview": "type TextColorOptions = {\n  type?: string;\n  value?: string | number | null | boolean;\n};\n\nexport const getTextColor = ("
  },
  {
    "path": "packages/jsoncrack-react/src/css-modules.d.ts",
    "chars": 101,
    "preview": "declare module \"*.module.css\" {\n  const classes: Record<string, string>;\n  export default classes;\n}\n"
  },
  {
    "path": "packages/jsoncrack-react/src/index.ts",
    "chars": 331,
    "preview": "export { JSONCrack } from \"./JSONCrackComponent\";\nexport { parseGraph } from \"./parser\";\nexport type { JSONCrackProps, J"
  },
  {
    "path": "packages/jsoncrack-react/src/parser.ts",
    "chars": 5561,
    "preview": "import { getNodePath, parseTree, type Node, type ParseError } from \"jsonc-parser\";\nimport type { EdgeData, GraphData, No"
  },
  {
    "path": "packages/jsoncrack-react/src/theme.ts",
    "chars": 1749,
    "preview": "import type { CanvasThemeMode } from \"./types\";\n\nexport interface JSONCrackTheme {\n  NODE_COLORS: {\n    TEXT: string;\n  "
  },
  {
    "path": "packages/jsoncrack-react/src/types.ts",
    "chars": 666,
    "preview": "import type { JSONPath, Node } from \"jsonc-parser\";\n\nexport interface NodeRow {\n  key: string | null;\n  value: string | "
  },
  {
    "path": "packages/jsoncrack-react/src/utils/calculateNodeSize.ts",
    "chars": 2508,
    "preview": "const NODE_DIMENSIONS = {\n  ROW_HEIGHT: 30,\n  PARENT_HEIGHT: 36,\n} as const;\n\ntype Text = number | string | [string, str"
  },
  {
    "path": "packages/jsoncrack-react/tsconfig.build.json",
    "chars": 440,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"lib\": [\"DOM\", \"ES2020\"],\n    \"module\": \"ESNext\",\n    \"moduleResolu"
  },
  {
    "path": "packages/jsoncrack-react/tsconfig.json",
    "chars": 149,
    "preview": "{\n  \"extends\": \"./tsconfig.build.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"src\"],\n  \"exclude\""
  }
]

// ... and 3 more files (download for full content)

About this extraction

This page contains the full source code of the AykutSarac/jsoncrack.com GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 203 files (349.7 KB), approximately 95.5k tokens, and a symbol index with 102 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!