Full Code of triggerdotdev/jsonhero-web for AI

main 15157053174b cached
215 files
517.7 KB
161.1k tokens
434 symbols
1 requests
Download .txt
Showing preview only (567K chars total). Download the full file or copy to clipboard to get everything.
Repository: triggerdotdev/jsonhero-web
Branch: main
Commit: 15157053174b
Files: 215
Total size: 517.7 KB

Directory structure:
gitextract_7rs4w8rw/

├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .vscode/
│   ├── launch.json
│   └── tasks.json
├── CONTRIBUTING.md
├── DEVELOPMENT.md
├── Dockerfile
├── LICENSE
├── README.md
├── SELF_HOSTING.md
├── app/
│   ├── bindings.d.ts
│   ├── components/
│   │   ├── AutoplayVideo.tsx
│   │   ├── BlankColumn.tsx
│   │   ├── CodeEditor.tsx
│   │   ├── CodeViewer.tsx
│   │   ├── Column.tsx
│   │   ├── ColumnItem.tsx
│   │   ├── Columns.tsx
│   │   ├── ContainerInfo.tsx
│   │   ├── CopySelectedNode.tsx
│   │   ├── CopyText.tsx
│   │   ├── CopyTextButton.tsx
│   │   ├── DataTable.tsx
│   │   ├── DocumentTitle.tsx
│   │   ├── DragAndDropForm.tsx
│   │   ├── ExampleDoc.tsx
│   │   ├── ExampleUrl.tsx
│   │   ├── FileSelector/
│   │   │   └── FileDropzone.tsx
│   │   ├── Footer.tsx
│   │   ├── Header.tsx
│   │   ├── Home/
│   │   │   ├── HomeApiHeroBanner.tsx
│   │   │   ├── HomeApiHeroLaptop.tsx
│   │   │   ├── HomeCollaborateSection.tsx
│   │   │   ├── HomeEdgeCasesSection.tsx
│   │   │   ├── HomeFeatureGridSection.tsx
│   │   │   ├── HomeFooter.tsx
│   │   │   ├── HomeGithubBanner.tsx
│   │   │   ├── HomeGridFeatureItem.tsx
│   │   │   ├── HomeHeader.tsx
│   │   │   ├── HomeHeroSection.tsx
│   │   │   ├── HomeInfoBoxSection.tsx
│   │   │   ├── HomeSearchSection.tsx
│   │   │   ├── HomeSection.tsx
│   │   │   └── HomeSplitSection.tsx
│   │   ├── Icons/
│   │   │   ├── ArrayIcon.tsx
│   │   │   ├── ArrowKeysIcon.tsx
│   │   │   ├── ArrowKeysUpDownIcon.tsx
│   │   │   ├── CopyShortcutIcon.tsx
│   │   │   ├── DiscordIcon.tsx
│   │   │   ├── DiscordIconTransparent.tsx
│   │   │   ├── EmailIcon.tsx
│   │   │   ├── EmailIconTransparent.tsx
│   │   │   ├── EscapeKeyIcon.tsx
│   │   │   ├── GithubIcon.tsx
│   │   │   ├── GithubIconSimple.tsx
│   │   │   ├── LoadingIcon.tsx
│   │   │   ├── Logo.tsx
│   │   │   ├── LogoTriggerdotdev.tsx
│   │   │   ├── MoonIcon.tsx
│   │   │   ├── ObjectIcon.tsx
│   │   │   ├── ShortcutIcon.tsx
│   │   │   ├── SquareBracketsIcon.tsx
│   │   │   ├── StringIcon.tsx
│   │   │   ├── SunIcon.tsx
│   │   │   ├── TreeIcon.tsx
│   │   │   └── TwitterIcon.tsx
│   │   ├── IndentPreference.tsx
│   │   ├── InfoHeader.tsx
│   │   ├── InfoPanel.tsx
│   │   ├── JsonColumnView.tsx
│   │   ├── JsonEditor.tsx
│   │   ├── JsonPreview.tsx
│   │   ├── JsonSchemaViewer.tsx
│   │   ├── JsonTreeView.tsx
│   │   ├── JsonView.tsx
│   │   ├── NewDocument.tsx
│   │   ├── NewFile.tsx
│   │   ├── OpenInWindow.tsx
│   │   ├── PathBar.tsx
│   │   ├── PathPreview.tsx
│   │   ├── PreferencesProvider.tsx
│   │   ├── Preview/
│   │   │   ├── CalendarMonth.tsx
│   │   │   ├── PreviewBox.tsx
│   │   │   ├── PreviewProperties.tsx
│   │   │   ├── PreviewValue.tsx
│   │   │   └── Types/
│   │   │       ├── PreviewAudioUri.tsx
│   │   │       ├── PreviewDate.tsx
│   │   │       ├── PreviewHtml.tsx
│   │   │       ├── PreviewIPFSImage.tsx
│   │   │       ├── PreviewImage.tsx
│   │   │       ├── PreviewImageUri.tsx
│   │   │       ├── PreviewJson.tsx
│   │   │       ├── PreviewString.tsx
│   │   │       ├── PreviewUri.tsx
│   │   │       ├── PreviewUriElement.tsx
│   │   │       ├── PreviewVideoUri.tsx
│   │   │       ├── RetweetIcon.tsx
│   │   │       └── preview.types.d.ts
│   │   ├── Primitives/
│   │   │   ├── Body.tsx
│   │   │   ├── BodyBold.tsx
│   │   │   ├── ExtraLargeTitle.tsx
│   │   │   ├── LargeMono.tsx
│   │   │   ├── LargeTitle.tsx
│   │   │   ├── Mono.tsx
│   │   │   ├── PageNotFoundTitle.tsx
│   │   │   ├── SmallBody.tsx
│   │   │   ├── SmallSubtitle.tsx
│   │   │   ├── SmallTitle.tsx
│   │   │   └── Title.tsx
│   │   ├── Properties/
│   │   │   ├── PropertiesFloat.tsx
│   │   │   ├── PropertiesInt.tsx
│   │   │   ├── PropertiesString.tsx
│   │   │   └── PropertiesValue.tsx
│   │   ├── RelatedValues.tsx
│   │   ├── Resizable.tsx
│   │   ├── SampleUrls.tsx
│   │   ├── SearchBar.tsx
│   │   ├── SearchPalette.tsx
│   │   ├── Share.tsx
│   │   ├── SideBar.tsx
│   │   ├── StarCountProvider.tsx
│   │   ├── ThemeModeToggle.tsx
│   │   ├── ThemeProvider.tsx
│   │   ├── ToolTip.tsx
│   │   ├── UI/
│   │   │   ├── Dialog.tsx
│   │   │   ├── GithubStar.tsx
│   │   │   ├── GithubStarSmall.tsx
│   │   │   ├── Popover.tsx
│   │   │   ├── Tabs.tsx
│   │   │   └── ToastPopover.tsx
│   │   ├── UrlForm.tsx
│   │   ├── ValueIcon.tsx
│   │   └── json-schema-map.d.ts
│   ├── entry.client.tsx
│   ├── entry.server.tsx
│   ├── entry.worker.ts
│   ├── graphJSON.server.ts
│   ├── hooks/
│   │   ├── useClickOutside.tsx
│   │   ├── useIsMounted.tsx
│   │   ├── useJson.tsx
│   │   ├── useJsonColumnView.tsx
│   │   ├── useJsonDoc.tsx
│   │   ├── useJsonSchema.tsx
│   │   ├── useJsonSearch.tsx
│   │   ├── useJsonTree.tsx
│   │   ├── useLoadWhenOnline.tsx
│   │   ├── useMemoCompare.ts
│   │   ├── useOnScreen.tsx
│   │   ├── useRelatedPaths.ts
│   │   ├── useSelectedInfo.tsx
│   │   └── useVirtualTree.ts
│   ├── jsonDoc.server.ts
│   ├── root.tsx
│   ├── routes/
│   │   ├── actions/
│   │   │   ├── $id/
│   │   │   │   └── update.ts
│   │   │   ├── createFromFile.ts
│   │   │   ├── createFromUrl.ts
│   │   │   ├── getPreview.$url.ts
│   │   │   └── setTheme.ts
│   │   ├── api/
│   │   │   └── create[.json].ts
│   │   ├── index.tsx
│   │   ├── j/
│   │   │   ├── $id/
│   │   │   │   ├── editor.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── terminal.tsx
│   │   │   │   └── tree.tsx
│   │   │   ├── $id.tsx
│   │   │   └── $id[.json].ts
│   │   ├── new.tsx
│   │   └── privacy.mdx
│   ├── services/
│   │   ├── apihero.server.ts
│   │   ├── github.server.ts
│   │   ├── toast.server.ts
│   │   └── uriPreview.server.ts
│   ├── theme.server.ts
│   ├── useColumnView/
│   │   └── index.ts
│   └── utilities/
│       ├── animationConstants.ts
│       ├── classnames.ts
│       ├── codeMirrorSetup.ts
│       ├── codeMirrorTheme.ts
│       ├── colors.ts
│       ├── dataType.ts
│       ├── formatStarCount.ts
│       ├── formatter.ts
│       ├── getRandomUserAgent.ts
│       ├── icons.ts
│       ├── inferredTemporal.ts
│       ├── jsonColumnView.ts
│       ├── nullable.ts
│       ├── relatedValues.ts
│       ├── safeFetch.ts
│       ├── search.ts
│       ├── stableJson.ts
│       └── xml/
│           ├── __test__/
│           │   ├── convertXmlToJsonString.test.ts
│           │   ├── isXML.test.ts
│           │   └── xml.txt
│           ├── convertFromRawXml.ts
│           ├── createFromRawXml.ts
│           └── isXML.ts
├── examples/
│   ├── owenWilsonWows.json
│   ├── pokemon.json
│   └── ronSwansonQuotes.json
├── package.json
├── remix.config.js
├── remix.env.d.ts
├── styles/
│   └── tailwind.css
├── tailwind.config.js
├── tests/
│   ├── formatStarCount.test.ts
│   ├── jsonColumnView.test.ts
│   ├── relatedValues.test.ts
│   ├── search.test.ts
│   ├── setup.js
│   └── stableJson.test.ts
├── tsconfig.json
├── worker/
│   └── index.js
├── wrangler.toml
└── wrangler.toml.dev

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

================================================
FILE: .github/workflows/main.yml
================================================
name: CI

on:
  push:
    branches: [main]
    paths:
      - ".github/workflows/main.yml"
      - "app/**/*.ts"
      - "app/**/*.tsx"
      - "public/*"
      - "styles/*"
      - "worker/*"
      - "tests/*"
      - "package.json"
      - "package-lock.json"
      - "remix.config.js"
      - "tsconfig.json"
      - "wrangler.toml"
      - "remix.env.d.ts"
      - "tailwind.config.js"

  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          registry-url: "https://registry.npmjs.org"
      - run: npm ci
      - run: npm test
      - run: npm run build
      - name: Publish app
        uses: cloudflare/wrangler-action@1.3.0
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          environment: "production"


================================================
FILE: .gitignore
================================================
node_modules

/.cache
/build
/public/build
.env
/app/tailwind.css
/jsonDocs
.DS_Store
/dist
.mf
/meta.json
/stats.html
public/entry.worker.js

================================================
FILE: .vscode/launch.json
================================================
{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "pwa-chrome",
      "request": "launch",
      "name": "Launch Chrome against localhost with document",
      "url": "http://localhost:8787",
      "webRoot": "${workspaceFolder}/app"
    },
    {
      "name": "Debug Jest All Tests",
      "type": "node",
      "request": "launch",
      "runtimeArgs": [
        "--inspect-brk",
        "${workspaceRoot}/node_modules/.bin/jest",
        "--runInBand"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    },
    {
      "name": "Debug Jest Test File",
      "type": "node",
      "request": "launch",
      "runtimeArgs": [
        "--inspect-brk",
        "${workspaceRoot}/node_modules/.bin/jest",
        "--runInBand"
      ],
      "args": ["${fileBasename}", "--no-cache"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}


================================================
FILE: .vscode/tasks.json
================================================
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "typescript",
      "tsconfig": "tsconfig.json",
      "option": "watch",
      "problemMatcher": ["$tsc-watch"],
      "group": "build",
      "label": "tsc: watch - tsconfig.json"
    }
  ]
}


================================================
FILE: CONTRIBUTING.md
================================================
## ⚡️ JSON Hero Contributing Guide

First of all, thanks for considering contributing to this project! If you have any questions please don't hesitate to reach out to [eric@jsonhero.io](mailto:eric@jsonhero.io) or join us on [Discord](https://discord.gg/JtBAxBr2m3).

JSON Hero is a Typescript React application built with [remix.run](https://remix.run), with support for deploying to Cloudflare workers.

To get started with contributing, please read our [Development guide](https://github.com/triggerdotdev/jsonhero-web/blob/main/DEVELOPMENT.md) first to get JSON Hero running locally.

### Running tests

Although there is less test-coverage for JSON Hero than there should be, tests should still be run to ensure builds have not been broken:

```bash
npm test
```

You can also run tests in "watch" mode:

```bash
npm run test:watch
```

### Making changes

Please make any changes to your forked repository in a branch other than `main`. If you are working on a bug fix, please use the `bug/` prefix for your branch name. If you are working on a feature, please use `features/`. If you are working on a specific issue please name the branch `issue-<issue number>`

Make sure to run the `npm lint` command to ensure there are no Typescript compile-time errors.

### Pull Requests

Please open a Pull Request against the `main` branch in the `triggerdotdev/jsonhero-web` repository. We will aim to address all newly opened PRs by the following Friday. If you haven't opened a Pull Request before, please check out GitHub's [Pull Request documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests)

### Other JSON Hero projects

If you'd like to contribute to the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=JSONHero.jsonhero-vscode), please see the [triggerdotdev/vscode-extension](https://github.com/triggerdotdev/vscode-extension) repo.

For issues related to the JSON Schema inference, please check out [triggerdotdev/schema-infer](https://github.com/triggerdotdev/schema-infer).

The "Smart Preview" feature is in-part powered by the [@jsonhero/json-infer-types](https://github.com/triggerdotdev/json-infer-types) project.

If it's related to the Search functionality, please see the [triggerdotdev/fuzzy-json-search](https://github.com/triggerdotdev/fuzzy-json-search) repo.


================================================
FILE: DEVELOPMENT.md
================================================
## 👩🏽‍💻 JSON Hero Local Development Guide

Welcome to JSON Hero development and thanks for being here! If you'd like to run JSON Hero locally, please use the following guide to get started. If you have any issues with this guide please feel free to email me at [eric@jsonhero.io](mailto:eric@jsonhero.io) or come leave a message in our open [Discord Channel](https://discord.gg/JtBAxBr2m3).

For more information about contributing to JSON Hero please see the [Contributing doc](https://github.com/triggerdotdev/jsonhero-web/blob/main/CONTRIBUTING.md).

### Install dependencies

Before you can run JSON Hero locally, you will need to install the following dependencies on your machine:

#### Git

You most likely already have git installed on your machine, but if not, you can install it from the [Git website](https://git-scm.com).

#### Node.js 16

Even though JSON Hero runs on [Cloudflare Workers](https://workers.cloudflare.com), which isn't a Node.js environment, you will still need Node.js 16 to run it locally. The recommended way to install Node.js is to download a pre-built package from the [Node.js website](https://nodejs.org/en/)

#### NPM

If you install Node.js through the above link, you should also have NPM automatically installed as well. To make sure, run the following command in your preferred Terminal:

```bash
npm ---version
```

### Fork JSON Hero on GitHub (optional)

To contribute code to JSON Hero, you should first create a fork of the [jsonhero-web](https://github.com/triggerdotdev/jsonhero-web) repository on GitHub. Follow [these instructions](https://docs.github.com/en/get-started/quickstart/fork-a-repo) on repository forking.

### Clone the repo

In your terminal, issue the following command to clone the repository to your local machine:

```bash
git clone https://github.com/triggerdotdev/jsonhero-web.git
```

Or if you've forked the repository:

```bash
git clone https://github.com/<github username>/jsonhero-web.git
```

Then `cd` into the repository:

```bash
cd jsonhero-web
```

### Prepare the repo

First, install npm dependencies:

```bash
npm install
```

Run the following command to create the `.env` file with a new `SESSION_SECRET` environment variable:

```bash
echo "SESSION_SECRET=$(openssl rand -hex 32)" > .env
```

Then, run `npm run build` or `npm run dev` to build.

Start the development server:

```bash
npm start
```

You should now be able to access your local JSON Hero server on [localhost:8787](http://localhost:8787)

> **Note** JSON documents created locally are not persisted across server restarts

### Previewing URLs

We currently use [OpenGraph Ninja](https://opengraph.ninja/) to power some of the Preview URL functionality.

### Deploying to Cloudflare

_Coming Soon_


================================================
FILE: Dockerfile
================================================
# Builder
FROM node:16.17.0 as builder
WORKDIR /src
COPY . /src

# App
RUN cd /src
RUN npm install
RUN echo "SESSION_SECRET=abc123" > .env
RUN npm run build

CMD npm start


================================================
FILE: LICENSE
================================================
                                 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 [yyyy] [name of copyright owner]

   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
================================================
<div align="center">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/4a157bda-2a99-4ac3-6bc7-be08b4a46600/public">
  <source media="(prefers-color-scheme: light)" srcset="https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/31447544-b16f-49dd-c206-74b1802c6700/public">
  <img width=200 alt="Trigger.dev logo" src="https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/4a157bda-2a99-4ac3-6bc7-be08b4a46600/public">
</picture>
</div>

</br>
<p align="center">
  <a href="https://console.algora.io/org/triggerdotdev/bounties?status=open"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Ftriggerdotdev%2Fbounties%3Fstatus%3Dopen" alt="Open Bounties" /></a>
  <a href="https://console.algora.io/org/triggerdotdev/bounties?status=completed"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Ftriggerdotdev%2Fbounties%3Fstatus%3Dcompleted" alt="Rewarded Bounties" /></a>
</p>

# Brought to you by Trigger.dev

JSON Hero was created and is maintained by the team behind [Trigger.dev](https://trigger.dev). With Trigger.dev you can trigger workflows from APIs, on a schedule, or on demand. We make API calls easy with authentication handled for you, and you can add durable delays that survive server restarts.

# JSON Hero

JSON Hero makes reading and understand JSON files easy by giving you a clean and beautiful UI packed with extra features.

- View JSON any way you'd like: Column View, Tree View, Editor View, and more.
- Automatically infers the contents of strings and provides useful previews
- Creates an inferred JSON Schema that could be used to validate your JSON
- Quickly scan related values to check for edge cases
- Search your JSON files (both keys and values)
- Keyboard accessible
- Easily sharable URLs with path support

![JSON Hero Screenshot](https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/0f5735b3-2421-470b-244c-7047fd77f700/public)

## Features

### Send to JSON Hero

Send your JSON to JSON Hero in a variety of ways

- Head to [jsonhero.io](https://jsonhero.io) and Drag and Drop a JSON file, or paste JSON or a JSON url in the provided form
- Include a Base64 encoded string of a JSON payload: [jsonhero.io/new?j=eyAiZm9vIjogImJhciIgfQ==](https://jsonhero.io/new?j=eyAiZm9vIjogImJhciIgfQ==)
- Include a JSON URL to the `new` endpoint: [jsonhero.io/new?url=https://jsonplaceholder.typicode.com/todos/1](https://jsonhero.io/new?url=https://jsonplaceholder.typicode.com/todos/1)
- Install the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=JSONHero.jsonhero-vscode) and open JSON from VS Code
- Raycast user? Check out our extension [here](https://www.raycast.com/maverickdotdev/open-in-json-hero)
- Use the unofficial API:

  - Make a `POST` request to `jsonhero.io/api/create.json` with the following JSON body:

  ```json
  {
    "title": "test 123",
    "content": { "foo": "bar" },
    "readOnly": false, // this is optional, will make it so the document title cannot be edited or document cannot be deleted
    "ttl": 3600 // this will expire the document after 3600 seconds, also optional
  }
  ```

  The JSON response will be the following:

  ```json
  {
    "id": "YKKduNySH7Ub",
    "title": "test 123",
    "location": "https://jsonhero.io/j/YKKduNySH7Ub"
  }
  ```

### Column view

Inspired by macOS Finder, Column View is a new way to browse a JSON document.

![JSON Hero Column View](https://raw.githubusercontent.com/triggerdotdev/documentation-hosting/main/images/features-columnview.gif)

It has all the features you'd expect: Keyboard navigation, Path bar, history.

It also has a nifty feature that allows you to "hold" a descendent selected and travel up through the hierarchy, and then move between siblings and view the different values found at that path. It's hard to describe, but here is an animation to help demonstrate:

![Column View - Traverse with Context](https://raw.githubusercontent.com/triggerdotdev/documentation-hosting/main/images/features-traversewithcontext.gif)

As you can see, holding the `Option` (or `Alt` key on Windows) while moving to a parent keeps the part of the document selected and shows it in context of it's surrounding JSON. Then you can traverse between items in an array and compare the values of the selection across deep hierarchy cahnges.

### Editor view

View your entire JSON document in an editor, but keep the nice previews and related values you get from the sidebar as you move around the document:

![Editor view](https://raw.githubusercontent.com/triggerdotdev/documentation-hosting/main/images/features-editorview.gif)

### Tree view

Use a traditional tree view to traverse your JSON document, with collapsible sections and keyboard shortcuts. All while keeping the nice previews:

![Tree view](https://raw.githubusercontent.com/triggerdotdev/documentation-hosting/main/images/features-treeview.gif)

### Search

Quickly open a search panel and fuzzy search your entire JSON file in milliseconds. Searches through key names, key paths, values, and even pretty formatted values (e.g. Searching for `"Dec"` will find datetime strings in the month of December.)

![Search](https://raw.githubusercontent.com/triggerdotdev/documentation-hosting/main/images/features-search.gif)

### Content Previews

JSON Hero automatically infers the content of strings and provides useful previews and properties of the value you've selected. It's "Show Don't Tell" for JSON:

#### Dates and Times

![Preview colors](https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/43f2c081-c09b-47db-cb10-8f15ee6a1a00/public)

#### Image URLs

![Preview colors](https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/8a743bd5-a065-4f7f-1262-585c39c10100/public)

#### Website URLs

![Preview websites](https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/cd7f2d28-2c8d-4b37-696d-e898937c3d00/public)

#### Tweet URLS

![Preview tweets](https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/8455e9d6-1d3e-451e-a032-f3259204ef00/public)

#### JSON URLs

![Preview JSON](https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/13743860-3d9c-4cac-dde9-881fba7eba00/public)

#### Colors

![Preview colors](https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/22e37599-c2bd-4abd-79f2-466241d17b00/public)

### Related Values

Easily see all the related values across your entire JSON document for a specific field, including any `undefined` or `null` values.

![Editor view](https://raw.githubusercontent.com/triggerdotdev/documentation-hosting/main/images/features-relatedvalues.gif)

<!-- TODO -->

## Bugs and Feature Requests

Have a bug or a feature request? Feel free to [open a new issue](https://github.com/triggerdotdev/jsonhero-web/issues).

You can also join our [Discord channel](https://discord.gg/JtBAxBr2m3) to hang out and discuss anything you'd like.

## Developing

To run locally, first clone the repo and install the dependencies:

```bash
git clone https://github.com/triggerdotdev/jsonhero-web.git
cd jsonhero-web
npm install
```

Then, create a file at the root of the repo called `.env` and set the `SESSION_SECRET` value:

```
SESSION_SECRET=abc123
```

Then, run `npm run build` or `npm run dev` to build.

Now, run `npm start` and open your browser to `http://localhost:8787`


================================================
FILE: SELF_HOSTING.md
================================================
## Deploying to Cloudflare

### Install and login to wrangler
```bash
npm install -g wrangler
wrangler login
```

### Create service
Go to workers tab from your [cloudflare profile](https://dash.cloudflare.com/profile) and create a new worker. Use HTTP Handler as service type. The name of worker must match the `name` field in `wrangler.toml`.

### Setup wrangler.toml
Edit the following variables in `wrangler.toml` and `wrangler.toml.dev`:
- `account_id`: Get account id by using
    ```bash
    wrangler whoami
    ```
- `kv_namespaces`: Run the following comands to create a new KV namespace.
    ```bash
    wrangler kv:namespace create DOCUMENTS # gives namespace id
    wrangler kv:namespace create DOCUMENTS --preview # gives preview id for namespace
    ```
    Replace current entry for `kv_namespaces` as:
    ```toml
    kv_namespaces = [
    { binding = "DOCUMENTS", id = <YOUR_ID>, preview_id = <YOUR_PREVIEW_ID> }
    ]
    ```

### Configure Environment Variables
Set `SESSION_SECRET` environment for worker.
```bash
wrangler secret put SESSION_SECRET
```
Optionally set other secrets listed at the end of `wrangler.toml`.

### Publish worker
```bash
wrangler publish
```


================================================
FILE: app/bindings.d.ts
================================================
export {};

declare global {
  const DOCUMENTS: KVNamespace;
  const SESSION_SECRET: string;
  const GRAPH_JSON_API_KEY: string;
  const GRAPH_JSON_COLLECTION: string;
  const APIHERO_PROJECT_KEY: string;
}


================================================
FILE: app/components/AutoplayVideo.tsx
================================================
import { useEffect, useRef } from "react";
import { useOnScreen } from "~/hooks/useOnScreen";

export function AutoplayVideo({ src }: { src: string }) {
  const elementRef = useRef<HTMLVideoElement>(null);
  const isOnScreen = useOnScreen(elementRef);

  useEffect(() => {
    if (elementRef.current == null) return;

    elementRef.current.muted = true;
    elementRef.current.playsInline = true;

    if (isOnScreen) {
      elementRef.current.play();
    } else {
      elementRef.current.pause();
    }
  }, [isOnScreen]);

  return (
    <video
      src={src}
      ref={elementRef}
      loop={true}
      muted={true}
      autoPlay={false}
    />
  );
}


================================================
FILE: app/components/BlankColumn.tsx
================================================
import { memo } from "react";

function BlankColumnElement() {
  return (
    <div
      className={
        "column flex-none border-r-[1px] border-slate-300 w-80 transition dark:border-slate-600"
      }
    ></div>
  );
}

export const BlankColumn = memo(BlankColumnElement);


================================================
FILE: app/components/CodeEditor.tsx
================================================
import { json as jsonLang } from "@codemirror/lang-json";
import {
  EditorView,
  TransactionSpec,
  useCodeMirror,
  ViewUpdate,
} from "@uiw/react-codemirror";
import { useRef, useEffect } from "react";
import { useJsonDoc } from "~/hooks/useJsonDoc";
import { getEditorSetup } from "~/utilities/codeMirrorSetup";
import { darkTheme, lightTheme } from "~/utilities/codeMirrorTheme";
import { useTheme } from "./ThemeProvider";
import { useHotkeys } from "react-hotkeys-hook";

export type CodeEditorProps = {
  content: string;
  language?: "json";
  readOnly?: boolean;
  onChange?: (value: string) => void;
  onUpdate?: (update: ViewUpdate) => void;
  selection?: { start: number; end: number };
};

const languages = {
  json: jsonLang,
};

type CodeEditorDefaultProps = Required<
  Omit<CodeEditorProps, "content" | "onChange" | "onUpdate">
>;

const defaultProps: CodeEditorDefaultProps = {
  language: "json",
  readOnly: true,
  selection: { start: 0, end: 0 },
};

export function CodeEditor(opts: CodeEditorProps) {
  const { content, language, readOnly, onChange, onUpdate, selection } = {
    ...defaultProps,
    ...opts,
  };

  const [theme] = useTheme();

  const extensions = getEditorSetup();

  const languageExtension = languages[language];

  extensions.push(languageExtension());

  const editor = useRef(null);
  const { setContainer, view, state } = useCodeMirror({
    container: editor.current,
    extensions,
    editable: !readOnly,
    contentEditable: !readOnly,
    value: content,
    autoFocus: false,
    theme: theme === "light" ? lightTheme() : darkTheme(),
    indentWithTab: false,
    basicSetup: false,
    onChange,
    onUpdate,
  });

  useEffect(() => {
    if (editor.current) {
      setContainer(editor.current);
    }
  }, [editor.current]);

  const setSelectionRef = useRef(false);

  useEffect(() => {
    if (setSelectionRef.current) {
      return;
    }

    if (view) {
      setSelectionRef.current = true;

      const selectionStart = selection?.start ?? defaultProps.selection.start;
      const selectionEnd = selection?.end ?? defaultProps.selection.end;

      const lineNumber = state?.doc.lineAt(selectionStart).number;

      const transactionSpec: TransactionSpec = {
        selection: { anchor: selectionStart, head: selectionEnd },
        effects: EditorView.scrollIntoView(selectionStart, {
          y: "start",
          yMargin: 100,
        }),
      };

      view.dispatch(transactionSpec);
    }
  }, [selection, view, setSelectionRef.current]);

  const { minimal } = useJsonDoc();

  useHotkeys(
   "ctrl+a,meta+a,command+a",
   (e) => {
     e.preventDefault();
     view?.dispatch({ selection: { anchor: 0, head: state?.doc.length } });
   },
   [view, state]
 );


  return (
    <div>
      <div
        className={`${
          minimal ? "h-jsonViewerHeightMinimal" : "h-jsonViewerHeight"
        } overflow-y-auto no-scrollbar`}
        ref={editor}
      />
    </div>
  );
}


================================================
FILE: app/components/CodeViewer.tsx
================================================
import { json as jsonLang } from "@codemirror/lang-json";
import { useCodeMirror } from "@uiw/react-codemirror";
import { useRef, useEffect } from "react";
import { getViewerSetup } from "~/utilities/codeMirrorSetup";
import { darkTheme, lightTheme } from "~/utilities/codeMirrorTheme";
import { useTheme } from "./ThemeProvider";
import { useHotkeys } from "react-hotkeys-hook";

export function CodeViewer({ code, lang }: { code: string; lang?: "json" }) {
  const editor = useRef(null);

  const extensions = getViewerSetup();

  if (!lang || lang === "json") {
    extensions.push(jsonLang());
  }

  const [theme] = useTheme();

  const { setContainer, view, state } = useCodeMirror({
    container: editor.current,
    extensions,
    value: code,
    editable: false,
    contentEditable: false,
    autoFocus: false,
    basicSetup: false,
    theme: theme === "light" ? lightTheme() : darkTheme(),
  });

  useEffect(() => {
    if (editor.current) {
      setContainer(editor.current);
    }
  }, [editor.current]);

  useHotkeys(
    "ctrl+a,meta+a,command+a",
    (e) => {
      e.preventDefault();
      view?.dispatch({ selection: { anchor: 0, head: state?.doc.length } });
    },
    [view, state]
  );

  return (
    <div>
      <div ref={editor} />
    </div>
  );
}


================================================
FILE: app/components/Column.tsx
================================================
import { Title } from "./Primitives/Title";
import { colorForItemAtPath } from "~/utilities/colors";
import { IconComponent } from "~/useColumnView";
import { useJson } from "../hooks/useJson";
import { memo, useMemo } from "react";
import { useJsonDoc } from "~/hooks/useJsonDoc";

export type ColumnProps = {
  id: string;
  title: string;
  icon?: IconComponent;
  hasHighlightedElement: boolean;
  children: React.ReactNode;
};

function ColumnElement(column: ColumnProps) {
  const { id, title, children } = column;
  const [json] = useJson();
  const { minimal } = useJsonDoc();
  const iconColor = useMemo(() => colorForItemAtPath(id, json), [id, json]);

  return (
    <div
      className={
        "column flex-none border-r-[1px] border-slate-300 w-80 transition dark:border-slate-600"
      }
    >
      <div className="flex items-center text-slate-800 bg-slate-50 mb-[3px] p-2 pb-0 transition dark:bg-slate-900 dark:text-slate-300">
        {column.icon && <column.icon className="h-6 w-6 mr-1" />}
        <Title className="text-ellipsis overflow-hidden">{title}</Title>
      </div>
      <div
        className={`overflow-y-auto ${
          minimal ? "h-viewerHeightMinimal" : "h-viewerHeight"
        } no-scrollbar`}
      >
        {children}
      </div>
    </div>
  );
}

export const Column = memo(ColumnElement);


================================================
FILE: app/components/ColumnItem.tsx
================================================
import { ChevronRightIcon } from "@heroicons/react/outline";
import { Mono } from "./Primitives/Mono";
import { memo, useEffect, useMemo, useRef } from "react";
import { ColumnViewNode } from "~/useColumnView";
import { colorForItemAtPath } from "~/utilities/colors";
import { Body } from "./Primitives/Body";

export type ColumnItemProps = {
  item: ColumnViewNode;
  json: unknown;
  isSelected: boolean;
  isHighlighted: boolean;
  onClick?: (id: string) => void;
};

function ColumnItemElement({
  item,
  json,
  isSelected,
  isHighlighted,
  onClick,
}: ColumnItemProps) {
  const htmlElement = useRef<HTMLDivElement>(null);

  const showArrow = item.children.length > 0;

  const stateStyle = useMemo<string>(() => {
    if (isHighlighted) {
      return "bg-slate-300 text-slate-700 hover:bg-slate-400 hover:bg-opacity-60 transition duration-75 ease-out dark:bg-white dark:bg-opacity-[15%] dark:text-slate-100";
    }

    if (isSelected) {
      return "bg-slate-200 hover:bg-slate-300 transition duration-75 ease-out dark:bg-white dark:bg-opacity-[5%] dark:hover:bg-white dark:hover:bg-opacity-[10%] dark:text-slate-200";
    }

    return "hover:bg-slate-100 transition duration-75 ease-out dark:hover:bg-white dark:hover:bg-opacity-[5%] dark:text-slate-400";
  }, [isSelected, isHighlighted]);

  const iconColor = useMemo<string>(
    () => colorForItemAtPath(item.id, json),
    [item.id, json]
  );

  useEffect(() => {
    if (isSelected || isHighlighted) {
      htmlElement.current?.scrollIntoView({
        block: "nearest",
        inline: "center",
      });
    }
  }, [isSelected, isHighlighted]);

  return (
    <div
      className={`flex h-9 items-center justify-items-stretch mx-1 px-1 py-1 my-1 rounded-sm ${stateStyle}`}
      onClick={() => onClick && onClick(item.id)}
      ref={htmlElement}
    >
      <div className="w-4 flex-none flex-col justify-items-center">
        {item.icon && (
          <item.icon
            className={`h-5 w-5 ${
              isSelected && isHighlighted
                ? "text-slate-900 dark:text-slate-300"
                : "text-slate-500"
            }`}
          />
        )}
      </div>

      <div className="flex flex-grow flex-shrink items-baseline justify-between truncate">
        <Body className="flex-grow flex-shrink-0 pl-3 pr-2 ">{item.title}</Body>
        {item.subtitle && (
          <Mono
            className={`truncate pr-1 transition duration-75 ${
              isHighlighted
                ? "text-gray-500 dark:text-slate-100"
                : "text-gray-400 dark:text-gray-500"
            }`}
          >
            {item.subtitle}
          </Mono>
        )}
      </div>

      {showArrow && (
        <ChevronRightIcon className="flex-none w-4 h-4 text-gray-400" />
      )}
    </div>
  );
}

export const ColumnItem = memo(ColumnItemElement);


================================================
FILE: app/components/Columns.tsx
================================================
import { JSONHeroPath } from "@jsonhero/path";
import { memo, useMemo } from "react";
import { useJson } from "~/hooks/useJson";
import {
  useJsonColumnViewAPI,
  useJsonColumnViewState,
} from "~/hooks/useJsonColumnView";
import { ColumnDefinition } from "~/useColumnView";
import { BlankColumn } from "./BlankColumn";
import { Column } from "./Column";
import { ColumnItem } from "./ColumnItem";

function ColumnsElement({ columns }: { columns: ColumnDefinition[] }) {
  const [json] = useJson();
  const { selectedPath, highlightedPath, highlightedNodeId } =
    useJsonColumnViewState();
  const { goToNodeId } = useJsonColumnViewAPI();
  const highlightedItemIsValue = useMemo<boolean>(() => {
    if (highlightedNodeId == null) {
      return false;
    }

    const path = new JSONHeroPath(highlightedNodeId);
    let item = path.first(json);

    return typeof item !== "object";
  }, [highlightedPath, json]);

  return (
    <div className="columns flex flex-grow overflow-x-auto focus:outline-none no-scrollbar">
      {columns.map((column) => {
        return (
          <Column
            key={column.id}
            id={column.id}
            title={column.title}
            icon={column.icon}
            hasHighlightedElement={
              highlightedPath[highlightedPath.length - 2] === column.id
            }
          >
            {column.items.map((item) => (
              <ColumnItem
                key={item.id}
                item={item}
                json={json}
                isSelected={selectedPath.includes(item.id)}
                isHighlighted={
                  highlightedPath[highlightedPath.length - 1] === item.id
                }
                onClick={(id) => goToNodeId(id, "columnView")}
              />
            ))}
          </Column>
        );
      })}
      {highlightedItemIsValue ? <BlankColumn /> : null}
    </div>
  );
}
export const Columns = memo(ColumnsElement);


================================================
FILE: app/components/ContainerInfo.tsx
================================================
import { inferType } from "@jsonhero/json-infer-types";
import { JSONHeroPath } from "@jsonhero/path";
import { useJson } from "~/hooks/useJson";
import { useJsonColumnViewState } from "~/hooks/useJsonColumnView";
import { pathToDescendant } from "~/utilities/jsonColumnView";
import { JsonPreview } from "./JsonPreview";
import { JsonSchemaViewer } from "./JsonSchemaViewer";
import { TabContent, Tabs } from "./UI/Tabs";

const tabs = [
  { value: "json", label: "JSON" },
  { value: "schema", label: "Schema" },
];

export function ContainerInfo() {
  const { selectedNodeId, highlightedNodeId } = useJsonColumnViewState();

  if (!selectedNodeId || !highlightedNodeId) {
    return <></>;
  }

  const [json] = useJson();

  const selectedHeroPath = new JSONHeroPath(selectedNodeId);
  const selectedJson = selectedHeroPath.first(json);
  const selectedInfo = inferType(selectedJson);

  const isSelectedLeafNode =
    selectedInfo.name !== "object" && selectedInfo.name !== "array";

  const highlightedHeroPath = new JSONHeroPath(highlightedNodeId);
  const highlightedJson = highlightedHeroPath.first(json);
  const highlightedInfo = inferType(highlightedJson);

  const isHighlightedLeafNode =
    highlightedInfo.name !== "object" && highlightedInfo.name !== "array";

  const shouldHighlightInPreview =
    selectedNodeId !== highlightedNodeId && !isHighlightedLeafNode;

  const shouldDisplayCodePreview =
    shouldHighlightInPreview || !isSelectedLeafNode;

  if (!shouldDisplayCodePreview) {
    return <></>;
  }

  return (
    <Tabs tabs={tabs}>
      <>
        <TabContent value="json">
          {shouldHighlightInPreview ? (
            <JsonPreview
              json={highlightedJson}
              highlightPath={pathToDescendant(
                highlightedNodeId,
                selectedNodeId
              )}
            />
          ) : (
            <JsonPreview json={selectedJson} />
          )}
        </TabContent>
        <TabContent value="schema">
          {shouldHighlightInPreview ? (
            <JsonSchemaViewer path={highlightedNodeId} />
          ) : (
            <JsonSchemaViewer path={selectedNodeId} />
          )}
        </TabContent>
      </>
    </Tabs>
  );
}


================================================
FILE: app/components/CopySelectedNode.tsx
================================================
import { useHotkeys } from "react-hotkeys-hook";
import { useSelectedInfo } from "../hooks/useSelectedInfo";

export function CopySelectedNodeShortcut() {
  const selectedInfo = useSelectedInfo();

  useHotkeys(
    'shift+c,shift+C',
    (e) => {
      e.preventDefault();
      const selectedJSON = selectedInfo?.name === "string"
        ? selectedInfo?.value
        : JSON.stringify(selectedInfo?.value, null, 2);
      navigator.clipboard.writeText(selectedJSON);
    },
    [selectedInfo]
  );

  return <></>;
}


================================================
FILE: app/components/CopyText.tsx
================================================
import React, { useCallback } from "react";

export type CopyTextProps = {
  children?: React.ReactNode;
  value: string;
  className?: string;
  onCopied?: () => void;
};

export function CopyText({
  children,
  value,
  className,
  onCopied,
}: CopyTextProps) {
  const onClick = useCallback(() => {
    navigator.clipboard.writeText(value);
    if (onCopied) {
      onCopied();
    }
  }, [value]);

  return (
    <div onClick={onClick} className={`${className}`}>
      {children}
    </div>
  );
}


================================================
FILE: app/components/CopyTextButton.tsx
================================================
import { ClipboardIcon } from "@heroicons/react/outline";
import { useCallback, useState } from "react";
import { CopyText } from "./CopyText";
import { Body } from "./Primitives/Body";

export type CopyTextButtonProps = {
  value: string;
  className?: string;
};

export function CopyTextButton({ value, className }: CopyTextButtonProps) {
  const [copied, setCopied] = useState(false);
  const onCopied = useCallback(() => {
    setCopied(true);
    const timeout = setTimeout(() => {
      setCopied(false);
    }, 1500);
  }, [value]);
  return (
    <CopyText className={`${className}`} value={value} onCopied={onCopied}>
      {copied ? (
        <Body>Copied!</Body>
      ) : (
        <div className="flex items-center">
          <ClipboardIcon className="h-4 w-4 mr-[2px]" />
          <Body>Copy</Body>
        </div>
      )}
    </CopyText>
  );
}


================================================
FILE: app/components/DataTable.tsx
================================================
import { FunctionComponent, useState } from "react";
import { CopyTextButton } from "./CopyTextButton";
import { Title } from "./Primitives/Title";

export type DataTableProps = {
  rows: DataTableRow[];
};

export type DataTableRow = {
  key: string;
  value: string;
  icon?: JSX.Element;
};

type DataRowProps = {
  title: string;
  value: string;
  icon?: JSX.Element;
};

const DataRow: FunctionComponent<DataRowProps> = ({ title, value, icon }) => {
  const [hovering, setHovering] = useState(false);
  return (
    <tr className="divide-solid divide-x transition dark:divide-slate-700">
      <td className="flex items-baseline py-2 pr-3 text-base dark:text-slate-400">
        <div className="flex-1 ml-1">{title}</div>
      </td>
      <td
        onMouseOver={() => setHovering(true)}
        onMouseOut={() => setHovering(false)}
        className={`relative w-full h-full pl-2 py-2 text-base text-slate-800 transition dark:text-slate-300 break-all ${
          hovering ? "bg-slate-100 dark:bg-slate-700" : "bg-transparent"
        }`}
      >
        {value}
        <div
          className={`absolute top-0 right-0 flex justify-end h-full w-full transition ${
            hovering ? "opacity-100" : "opacity-0"
          }`}
        >
          <CopyTextButton
            className="bg-slate-200 hover:bg-slate-300 h-fit mt-1 mr-1 px-2 py-0.5 rounded-sm transition hover:cursor-pointer dark:text-white dark:bg-slate-600 dark:hover:bg-slate-500"
            value={value}
          ></CopyTextButton>
        </div>
      </td>
    </tr>
  );
};

export const DataTable: FunctionComponent<DataTableProps> = ({ rows }) => {
  return (
    <div>
      <Title className="text-slate-700 dark:text-slate-400 mb-2">
        Properties
      </Title>
      <table className="w-full table-auto border-y-[0.5px] border-slate-300 transition dark:border-slate-700">
        <tbody className="divide-solid divide-y divide-slate-300 w-full transition dark:divide-slate-700">
          {rows.map((row) => {
            return (
              <DataRow
                key={row.key}
                title={row.key}
                value={row.value}
                icon={row.icon}
              />
            );
          })}
        </tbody>
      </table>
    </div>
  );
};


================================================
FILE: app/components/DocumentTitle.tsx
================================================
import { PencilAltIcon } from "@heroicons/react/outline";
import { useEffect, useRef, useState } from "react";
import { useFetcher } from "remix";
import { match } from "ts-pattern";
import { useJsonDoc } from "~/hooks/useJsonDoc";

export function DocumentTitle() {
  const { doc } = useJsonDoc();
  const [editedTitle, setEditedTitle] = useState(doc.title);
  const updateDoc = useFetcher();
  const ref = useRef<HTMLInputElement | null>(null);

  useEffect(() => {
    if (updateDoc.type === "done" && updateDoc.data.title) {
      ref.current?.blur();
    }
  }, [updateDoc]);

  if (doc.readOnly) {
    return (
      <div
        className="flex justify-center items-center w-full"
        title={doc.title}
      >
        <span
          className={
            "min-w-[15vw] border-none text-ellipsis text-slate-300 px-2 pl-10 py-1 rounded-sm bg-transparent placeholder:text-slate-400 focus:bg-black/30 focus:outline-none focus:border-none hover:cursor-text transition dark:bg-transparent dark:text-slate-200 dark:placeholder:text-slate-400 dark:focus:bg-black dark:focus:bg-opacity-10"
          }
        >
          {doc.title}
        </span>
      </div>
    );
  } else {
    return (
      <updateDoc.Form method="post" action={`/actions/${doc.id}/update`}>
        <div
          className="flex justify-center items-center w-full"
          title={doc.title}
        >
          <label className="relative block group">
            <PencilAltIcon className="h-5 w-5 absolute top-1/2 transform -translate-y-1/2 left-3 text-white opacity-0 transition pointer-events-none group-hover:opacity-80 group-focus:opacity-80" />
            <input
              ref={ref}
              className={
                "min-w-[15vw] border-none text-ellipsis text-slate-300 px-2 pl-10 py-1 rounded-sm bg-transparent placeholder:text-slate-400 focus:bg-black/30 focus:outline-none focus:border-none hover:bg-black hover:bg-opacity-30 hover:cursor-text transition dark:bg-transparent dark:text-slate-200 dark:placeholder:text-slate-400 dark:focus:bg-black dark:focus:bg-opacity-10 dark:hover:bg-black dark:hover:bg-opacity-10"
              }
              type="text"
              name="title"
              spellCheck="false"
              placeholder="Name your JSON file"
              value={editedTitle}
              onChange={(e) => setEditedTitle(e.target.value)}
            />
          </label>

          {match(editedTitle)
            .with(doc.title, () => (
              <p className="ml-2 text-transparent">Save</p>
            ))
            .with("", () => (
              <button
                className="ml-2 text-lime-500 hover:text-lime-600 transition"
                onClick={() => setEditedTitle(doc.title)}
              >
                Reset
              </button>
            ))
            .otherwise(() => (
              <button
                type="submit"
                className="ml-2 text-lime-500 hover:text-lime-600 transition"
              >
                Save
              </button>
            ))}
        </div>
      </updateDoc.Form>
    );
  }
}


================================================
FILE: app/components/DragAndDropForm.tsx
================================================
import { ArrowCircleDownIcon } from "@heroicons/react/outline";
import { useCallback, useRef } from "react";
import { useDropzone } from "react-dropzone";
import { Form, useSubmit } from "remix";
import invariant from "tiny-invariant";

export function DragAndDropForm() {
  const formRef = useRef<HTMLFormElement>(null);
  const filenameInputRef = useRef<HTMLInputElement>(null);
  const rawJsonInputRef = useRef<HTMLInputElement>(null);

  const submit = useSubmit();

  const onDrop = useCallback(
    (acceptedFiles: Array<File>) => {
      if (!formRef.current || !filenameInputRef.current) {
        return;
      }

      if (acceptedFiles.length === 0) {
        return;
      }

      const firstFile = acceptedFiles[0];

      const reader = new FileReader();

      reader.onabort = () => console.log("file reading was aborted");
      reader.onerror = () => console.log("file reading has failed");
      reader.onload = () => {
        if (reader.result == null) {
          return;
        }

        let jsonValue: string | undefined = undefined;

        if (typeof reader.result === "string") {
          jsonValue = reader.result;
        } else {
          const decoder = new TextDecoder("utf-8");
          jsonValue = decoder.decode(reader.result);
        }

        invariant(rawJsonInputRef.current, "rawJsonInputRef is null");
        invariant(jsonValue, "jsonValue is undefined");

        rawJsonInputRef.current.value = jsonValue;

        submit(formRef.current);
      };
      reader.readAsArrayBuffer(firstFile);
      filenameInputRef.current.value = firstFile.name;
    },
    [formRef.current, filenameInputRef.current, rawJsonInputRef.current]
  );

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDropAccepted: onDrop,
    maxFiles: 1,
    maxSize: 1024 * 1024 * 1,
    multiple: false,
    accept: "application/json",
  });

  return (
    <Form method="post" action="/actions/createFromFile" ref={formRef}>
      <div
        {...getRootProps()}
        className="block min-w-[300px] cursor-pointer rounded-md border-2 border-dashed border-slate-600 bg-slate-900/40 p-4 text-base text-slate-300 focus:border-indigo-500 focus:ring-indigo-500"
      >
        <input {...getInputProps()} />
        <div className="flex items-center">
          <ArrowCircleDownIcon
            className={`mr-3 inline h-6 w-6 ${
              isDragActive ? "text-lime-500" : ""
            }`}
          />
          <p className={`${isDragActive ? "text-lime-500" : ""}`}>
            {isDragActive
              ? "Now drop to open it…"
              : "Drop a JSON file here, or click to select"}
          </p>
        </div>

        <input type="hidden" name="filename" ref={filenameInputRef} />
        <input type="hidden" name="rawJson" ref={rawJsonInputRef} />
      </div>
    </Form>
  );
}


================================================
FILE: app/components/ExampleDoc.tsx
================================================
import { Link } from "remix";

export function ExampleDoc({
  id,
  title,
  path,
}: {
  id: string;
  title: string;
  path?: string;
}) {
  return (
    <Link
      to={`/j/${id}${path ? `?path=${path}` : ""}`}
      className="bg-slate-900 px-4 py-2 rounded-md whitespace-nowrap text-lime-300 transition hover:text-lime-500"
    >
      {title}
    </Link>
  );
}


================================================
FILE: app/components/ExampleUrl.tsx
================================================
import { Form } from "remix";

export function ExampleUrl({
  url,
  title,
  displayTitle,
}: {
  url: string;
  title: string;
  displayTitle?: string;
}) {
  return (
    <Form
      method="post"
      action="/actions/createFromUrl?utm_source=example_url"
      reloadDocument
    >
      <input type="hidden" name="jsonUrl" value={url} />
      <input type="hidden" name="title" value={title} />
      <button
        type="submit"
        className="bg-slate-900 px-4 py-2 rounded-md whitespace-nowrap text-lime-300 transition hover:text-lime-500"
      >
        {displayTitle ?? title}
      </button>
    </Form>
  );
}


================================================
FILE: app/components/FileSelector/FileDropzone.tsx
================================================
import { FunctionComponent, useCallback } from "react";
import { useDropzone } from "react-dropzone";
import { DocumentDownloadIcon } from "@heroicons/react/outline";

export const FileDropzone: FunctionComponent = ({ children }) => {
  const onDrop = useCallback((acceptedFiles) => {
    acceptedFiles.forEach((file: Blob) => {
      const reader = new FileReader();
      reader.onabort = () => console.log("file reading was aborted");
      reader.onerror = () => console.log("file reading has failed");
      reader.onload = () => {
        if (typeof reader.result === "string") {
          let json = JSON.parse(reader.result);
          // dataSourceDispatch(setJSONAction("Needs title", json));
        } else {
          // dataSourceDispatch(setErrorAction("Can't read file"));
        }
      };
      reader.readAsText(file);
    });
  }, []);

  const { getRootProps, isDragActive } = useDropzone({
    onDrop,
    multiple: false,
    maxFiles: 1,
    accept: "application/json, text/*",
    noDragEventsBubbling: true,
  });

  return (
    <div
      {...getRootProps()}
      className={"absolute w-screen h-screen m-0 p-0 left-0 top-0"}
    >
      <div
        className={`${
          isDragActive ? "" : "hidden"
        } absolute w-screen h-screen bg-black bg-opacity-50 flex justify-center items-center`}
      >
        <div className={"text-center"}>
          {/*<input {...getInputProps()} />*/}
          <DocumentDownloadIcon className={"w-72 h-72 text-white"} />
          <p className={"text-white text-2xl"}>
            Drag 'n' drop some files here, or click to select files
          </p>
        </div>
      </div>
      <div>{children}</div>
    </div>
  );
};


================================================
FILE: app/components/Footer.tsx
================================================
import { useJsonDoc } from "~/hooks/useJsonDoc";
import { ArrowKeysIcon } from "./Icons/ArrowKeysIcon";
import { CopyShortcutIcon } from "./Icons/CopyShortcutIcon";
import { EscapeKeyIcon } from "./Icons/EscapeKeyIcon";
import { SquareBracketsIcon } from "./Icons/SquareBracketsIcon";
import { Body } from "./Primitives/Body";
import { ThemeModeToggler } from "./ThemeModeToggle";
import { GithubStarSmall } from "./UI/GithubStarSmall";
import { IndentPreference } from "~/components/IndentPreference";
import { ArrowRightIcon } from "@heroicons/react/outline";
import TriggerDevLogoImageDark from "~/assets/images/trigger-dev-logo-dark.png";
import TriggerDevLogoImage from "~/assets/images/trigger-dev-logo.png";
import TriggerDevLogoTriangleImage from "~/assets/images/td-triangle.png";

export function Footer() {
  const { minimal } = useJsonDoc();

  return (
    <footer className="flex items-center justify-between w-screen h-[32px] flex-shrink-0 bg-slate-200 dark:bg-slate-800 border-t-[1px] border-slate-400 transition dark:border-slate-600">
      <ol className="flex pl-3">
        <li className="flex items-center">
          <ArrowKeysIcon className="transition text-slate-300 dark:text-slate-500" />
          <Body className="pl-2 pr-4 text-slate-800 transition dark:text-white">
            Navigate
          </Body>
        </li>
        <li className="flex items-center">
          <SquareBracketsIcon className="transition text-slate-300 dark:text-slate-500" />
          <Body className="pl-2 pr-4 text-slate-800 transition dark:text-white">
            History
          </Body>
        </li>
        <li className="flex items-center">
          <EscapeKeyIcon className="transition text-slate-300 dark:text-slate-500" />
          <Body className="pl-2 pr-4 text-slate-800 transition dark:text-white whitespace-nowrap">
            Reset path
          </Body>
        </li>
        <li className="flex items-center">
          <CopyShortcutIcon className="transition text-slate-300 dark:text-slate-500" />
          <Body className="flex pl-2 pr-4 text-slate-800 transition dark:text-white">
            Copy&nbsp;
            <span className="hidden lg:flex whitespace-nowrap">
              selected&nbsp;
            </span>
            node
          </Body>
        </li>
      </ol>
      <ol className="flex gap-2 items-center h-full invisible md:visible">
        {minimal && (
          <li>
            <GithubStarSmall />
          </li>
        )}
        <li>
          <IndentPreference />
        </li>
        <li>
          <ThemeModeToggler />
        </li>
      </ol>
    </footer>
  );
}


================================================
FILE: app/components/Header.tsx
================================================
import { ShareIcon, PlusIcon, TrashIcon } from "@heroicons/react/outline";
import { DocumentTitle } from "./DocumentTitle";
import { DiscordIconTransparent } from "./Icons/DiscordIconTransparent";
import { EmailIconTransparent } from "./Icons/EmailIconTransparent";
import { GithubStar } from "./UI/GithubStar";
import { Logo } from "./Icons/Logo";
import { Share } from "./Share";
import { NewDocument } from "./NewDocument";
import {
  Popover,
  PopoverArrow,
  PopoverContent,
  PopoverTrigger,
} from "./UI/Popover";
import { Form } from "remix";
import { useJsonDoc } from "~/hooks/useJsonDoc";
import { LogoTriggerdotdev } from "./Icons/LogoTriggerdotdev";

export function Header() {
  const { doc } = useJsonDoc();

  return (
    <header className="flex items-center justify-between w-screen h-[40px] bg-indigo-700 dark:bg-slate-800 border-b-[1px] border-slate-600">
      <div className="flex pl-2 gap-1 sm:gap-1.5 pt-0.5 h-8 justify-center items-center">
        <div className="w-20 sm:w-24">
          <Logo />
        </div>
        <p className="text-slate-300 text-sm font-sans">by</p>
        <LogoTriggerdotdev className="w-16 sm:w-20 opacity-80 hover:opacity-100  transition duration-300" />
      </div>
      <DocumentTitle />
      <ol className="flex text-sm items-center gap-2 px-4">
        {!doc.readOnly && (
          <Form
            method="delete"
            onSubmit={(e) =>
              !confirm(
                "This will permanantly delete this document from jsonhero.io, are you sure you want to continue?"
              ) && e.preventDefault()
            }
          >
            <button type="submit">
              <button className="flex items-center justify-center py-1 bg-slate-200 text-slate-800 bg-opacity-80 text-base font-bold px-2 rounded uppercase hover:cursor-pointer hover:bg-opacity-100 transition">
                <TrashIcon className="w-4 h-4 mr-0.5"></TrashIcon>
                Delete
              </button>
            </button>
          </Form>
        )}

        <Popover>
          <PopoverTrigger>
            <button className="flex items-center justify-center bg-lime-500 text-slate-800 bg-opacity-90 text-base font-bold px-2 py-1 rounded uppercase hover:cursor-pointer hover:bg-opacity-100 transition">
              <PlusIcon className="w-4 h-4 mr-0.5"></PlusIcon>
              New
            </button>
          </PopoverTrigger>
          <PopoverContent side="bottom" sideOffset={8}>
            <NewDocument />
            <PopoverArrow
              className="fill-current text-indigo-700"
              offset={20}
            />
          </PopoverContent>
        </Popover>

        <Popover>
          <PopoverTrigger>
            <button className="flex items-center justify-center py-1 bg-slate-200 text-slate-800 bg-opacity-90 text-base font-bold px-2 rounded uppercase hover:cursor-pointer hover:bg-opacity-100 transition">
              <ShareIcon className="w-4 h-4 mr-1"></ShareIcon>
              Share
            </button>
          </PopoverTrigger>
          <PopoverContent side="bottom" sideOffset={8}>
            <Share />
            <PopoverArrow
              className="fill-current text-indigo-700"
              offset={20}
            />
          </PopoverContent>
        </Popover>

        <li className="opacity-90 transition hover:cursor-pointer hover:opacity-100">
          <GithubStar />
        </li>
        <li className="opacity-90 transition hover:cursor-pointer hover:opacity-100">
          <a href="https://discord.gg/JtBAxBr2m3" target="_blank">
            <DiscordIconTransparent />
          </a>
        </li>
      </ol>
    </header>
  );
}


================================================
FILE: app/components/Home/HomeApiHeroBanner.tsx
================================================
import { Body } from "../Primitives/Body";
import { HomeApiHeroLaptop } from "./HomeApiHeroLaptop";

export function HomeApiHeroBanner() {
  return (
    <div className="flex items-center justify-start md:justify-center w-full h-40 bg-gradient-to-r from-purple-600 via-pink-500 to-purple-600 hover:backdrop-filter hover:backdrop-brightness-75 transition">
      <div className="relative flex justify-center items-center w-1/2 md:w-full pl-6 md:px-6">
        <div className="flex flex-col">
          <Body className=" text-white text-[1rem] sm:text-[1.2rem] font-bold md:text-3xl leading-tight">
            Early access to ⚡️ API Hero
          </Body>
          <p className="mb-2 text-white md:text-xl text-sm">
            Make every API you use faster and more reliable.
          </p>
          <a
            href="https://apihero.run"
            target="new"
            className="flex items-center justify-center px-3 py-2 mt-2 text-center text-md md:text-xl text-slate-800 font-bold bg-lime-500 rounded shadow-md hover:bg-lime-400 transition"
          >
            Get started &rarr;
          </a>
        </div>
        <a
          href="https://apihero.run"
          target="new"
          className="absolute md:relative -top-5 md:top-auto -right-[20rem] md:right-auto"
        >
          <HomeApiHeroLaptop className="w-50 md:w-80 mb-2"></HomeApiHeroLaptop>
        </a>
      </div>
    </div>
  );
}


================================================
FILE: app/components/Home/HomeApiHeroLaptop.tsx
================================================
import ApiHeroLaptop from "~/assets/images/apihero-laptop.png";

export type IconProps = {
  className?: string;
};

export function HomeApiHeroLaptop({ className }: IconProps) {
  return <img src={ApiHeroLaptop} className={className} />;
}


================================================
FILE: app/components/Home/HomeCollaborateSection.tsx
================================================
import { AutoplayVideo } from "../AutoplayVideo";
import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
import { SmallSubtitle } from "../Primitives/SmallSubtitle";
import { HomeSection } from "./HomeSection";

import shareVideo from "~/assets/home/JsonHeroShare.mp4";

export function HomeCollaborateSection() {
  return (
    <HomeSection
      containerClassName="py-10 px-6 bg-black md:py-36 lg:py-20"
      reversed
    >
      <div className="w-full md:pl-10 md:w-1/2">
        <ExtraLargeTitle className="text-white mb-4">
          Collaborate with the whole world (and yourself)
        </ExtraLargeTitle>
        <SmallSubtitle className="mb-6 md:mb-10">
          Easily share your JSON documents with any distant relative. Link right
          to the part of the document you're on. Or save the link for some
          casual browsing later in the evening while enjoying a glass of red.
        </SmallSubtitle>
      </div>
      <div className="w-full md:w-1/2">
        <AutoplayVideo src={shareVideo} />
      </div>
    </HomeSection>
  );
}


================================================
FILE: app/components/Home/HomeEdgeCasesSection.tsx
================================================
import { AutoplayVideo } from "../AutoplayVideo";
import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
import { SmallSubtitle } from "../Primitives/SmallSubtitle";
import { HomeSection } from "./HomeSection";

import edgeCasesVideo from "~/assets/home/UncoverEdgeCases.mp4";

export function HomeEdgeCasesSection() {
  return (
    <HomeSection
      containerClassName="py-10 px-6 bg-black md:py-36 lg:py-20"
      reversed
    >
      <div className="w-full md:pl-10 md:w-1/2">
        <ExtraLargeTitle className="text-white mb-4">
          Uncover edge cases
        </ExtraLargeTitle>
        <SmallSubtitle className="mb-6 md:mb-10">
          Sometimes a field can be null, have an unexpected value or be missing
          entirely. View any field's related values and see what to expect when
          you least expect it. Or check out the inferred JSON schema to see what
          your JSON is really made of.
        </SmallSubtitle>
      </div>
      <div className="w-full md:w-1/2">
        <AutoplayVideo src={edgeCasesVideo} />
      </div>
    </HomeSection>
  );
}


================================================
FILE: app/components/Home/HomeFeatureGridSection.tsx
================================================
import {
  FastForwardIcon,
  MoonIcon,
  ClockIcon,
  CodeIcon,
  LockOpenIcon,
  CubeTransparentIcon,
} from "@heroicons/react/outline";
import { Body } from "../Primitives/Body";
import { LargeTitle } from "../Primitives/LargeTitle";
import { HomeGridFeatureItem } from "./HomeGridFeatureItem";
import { HomeSection } from "./HomeSection";

export function HomeFeatureGridSection() {
  return (
    <HomeSection containerClassName="bg-black">
      <div className="flex flex-col px-4 pb-2 pt-6 md:py-12">
        <LargeTitle className="mb-4 text-slate-300">
          And lots more features…
        </LargeTitle>
        <div className="flex flex-col gap-4 md:flex-row md:flex-wrap">
          <HomeGridFeatureItem
            icon={FastForwardIcon}
            title="Keyboard shortcuts"
            titleClassName="text-white"
          >
            <Body className="text-slate-400">
              Move as fast as you can think… after 3 coffees
            </Body>
          </HomeGridFeatureItem>

          <HomeGridFeatureItem
            icon={MoonIcon}
            title="Dark mode"
            titleClassName="text-white"
          >
            <Body className="text-slate-400">
              Of course, we’re not animals.
            </Body>
          </HomeGridFeatureItem>

          <HomeGridFeatureItem
            icon={ClockIcon}
            title="Code view"
            titleClassName="text-white"
          >
            <Body className="text-slate-400">
              Easily switch to the code view, so you can appear hardcore.
            </Body>
          </HomeGridFeatureItem>
          <HomeGridFeatureItem
            icon={CubeTransparentIcon}
            title="Auto JSON Schema"
            titleClassName="text-white"
          >
            <Body className="text-slate-400">
              Automatically generates JSON Schema (draft 2020-12) from your
              JSON.
            </Body>
          </HomeGridFeatureItem>
          <HomeGridFeatureItem
            icon={CodeIcon}
            title="VS Code plugin"
            titleClassName="text-white"
          >
            <Body className="text-slate-400">
              Quickly view JSON files or selections in JSON Hero, right from VS
              Code.{" "}
              <a
                className="whitespace-nowrap text-lime-300 hover:text-lime-500"
                href="https://marketplace.visualstudio.com/items?itemName=JSONHero.jsonhero-vscode"
                target="_blank"
                rel="noopener noreferrer"
              >
                Get it here
              </a>
              .
            </Body>
          </HomeGridFeatureItem>
          <HomeGridFeatureItem
            icon={LockOpenIcon}
            title="100% open source"
            titleClassName="text-white"
          >
            <Body className="text-slate-400">
              Use jsonhero.io or fork it on GitHub and run it yourself.
            </Body>
          </HomeGridFeatureItem>
        </div>
      </div>
    </HomeSection>
  );
}


================================================
FILE: app/components/Home/HomeFooter.tsx
================================================
import { Link } from "remix";
import { DiscordIcon } from "../Icons/DiscordIcon";
import { EmailIcon } from "../Icons/EmailIcon";
import { GithubIcon } from "../Icons/GithubIcon";
import { Logo } from "../Icons/Logo";
import { TwitterIcon } from "../Icons/TwitterIcon";

export type HomeFooterProps = {
  maxWidth?: string;
};

export function HomeFooter({ maxWidth = "1150px" }: HomeFooterProps) {
  return (
    <footer className="flex flex-col items-center w-full px-4 py-6 bg-black md:py-10">
      <div
        className="flex items-center justify-between w-full border-t-[1px] pt-9 border-slate-800"
        style={{ maxWidth: maxWidth }}
      >
        <div className="flex flex-grow items-start">
          <Logo />
        </div>
        <ol className="flex ml-2">
          <li className="mr-2 hover:cursor-pointer text-white/70 hover:text-white transition">
            <Link to="/privacy">Privacy</Link>
          </li>
          <li className="hover:cursor-pointer">
            <a
              href="https://github.com/triggerdotdev/jsonhero-web"
              target="_blank"
            >
              <GithubIcon />
            </a>
          </li>
          <li className="ml-2 hover:cursor-pointer">
            <a href="mailto:hello@jsonhero.io">
              <EmailIcon />
            </a>
          </li>
          <li className="ml-2 hover:cursor-pointer">
            <a href="https://discord.gg/JtBAxBr2m3" target="_blank">
              <DiscordIcon />
            </a>
          </li>
          <li className="ml-2 hover:cursor-pointer">
            <a href="https://twitter.com/triggerdotdev" target="_blank">
              <TwitterIcon />
            </a>
          </li>
        </ol>
      </div>
    </footer>
  );
}


================================================
FILE: app/components/Home/HomeGithubBanner.tsx
================================================
import { Body } from "../Primitives/Body";
import { GithubStar } from "../UI/GithubStar";

export function GithubBanner() {
  return (
    <div className="flex items-center justify-center w-full h-14 bg-indigo-600">
      <div className="flex items-center">
        <Body className="mr-3 text-xl text-white">Star us on GitHub 👉</Body>
        <GithubStar />
      </div>
    </div>
  );
}


================================================
FILE: app/components/Home/HomeGridFeatureItem.tsx
================================================
import { IconComponent } from "~/useColumnView";
import { Body } from "../Primitives/Body";
import { Title } from "../Primitives/Title";

export type HomeGridFeatureItemProps = {
  icon: IconComponent;
  title: string;
  className?: string;
  titleClassName?: string;
  children: React.ReactNode;
};

export function HomeGridFeatureItem(props: HomeGridFeatureItemProps) {
  return (
    <div className="flex lg:basis-1/4 basis-1 md:basis-1/4 flex-grow flex-col p-6 rounded-sm bg-white bg-opacity-[7%]">
      <props.icon className="w-10 h-10 min-h-[44px] text-indigo-700 mb-3" />
      <Title className={props.titleClassName}>{props.title}</Title>
      {props.children}
    </div>
  );
}


================================================
FILE: app/components/Home/HomeHeader.tsx
================================================
import { DiscordIconTransparent } from "../Icons/DiscordIconTransparent";
import { EmailIconTransparent } from "../Icons/EmailIconTransparent";
import { TwitterIcon } from "../Icons/TwitterIcon";
import { Logo } from "../Icons/Logo";
import { NewDocument } from "../NewDocument";
import { GithubStar } from "../UI/GithubStar";
import {
  Popover,
  PopoverArrow,
  PopoverContent,
  PopoverTrigger,
} from "../UI/Popover";
import TriggerDevLogoImage from "~/assets/images/trigger-dev-logo.png";
import { LogoTriggerdotdev } from "../Icons/LogoTriggerdotdev";

export function HomeHeader({ fixed }: { fixed?: boolean }) {
  return (
    <header
      className={`${
        fixed ? "fixed" : ""
      } z-20 flex h-12 justify-center  bg-indigo-700 flex-col`}
    >
      <div className="flex items-center justify-between w-screen px-4">
        <div className="flex gap-1 sm:gap-1.5 h-8 justify-center items-center">
          <div className="w-24 sm:w-32">
            <Logo />
          </div>
          <p className="text-slate-300 text-sm sm:text-base font-sans">by</p>
          <LogoTriggerdotdev className="pt-0.5 w-16 sm:w-24 opacity-80 hover:opacity-100 transition duration-300" />
        </div>
        <ol className="flex items-center gap-2 sm:pr-4">
          <Popover>
            <PopoverTrigger>
              <button className=" bg-lime-400 text-slate-900 text-lg font-bold px-2 py-0.5 rounded uppercase whitespace-nowrap cursor-pointer opacity-90 hover:opacity-100 transition">
                Try now
              </button>
            </PopoverTrigger>
            <PopoverContent side="bottom" sideOffset={30}>
              <NewDocument />
              <PopoverArrow
                className="fill-current text-indigo-700"
                offset={20}
              />
            </PopoverContent>
          </Popover>

          <li className="hover:cursor-pointer hidden sm:block">
            <GithubStar />
          </li>
          <li className="hover:cursor-pointer opacity-90 hover:opacity-100 transition hidden sm:block">
            <a href="mailto:hello@jsonhero.io">
              <EmailIconTransparent />
            </a>
          </li>
          <li className="hover:cursor-pointer opacity-90 hover:opacity-100 transition hidden sm:block">
            <a href="https://discord.gg/JtBAxBr2m3" target="_blank">
              <DiscordIconTransparent />
            </a>
          </li>
          <li className="hover:cursor-pointer opacity-90 hover:opacity-100 transition hidden sm:block">
            <a href="https://twitter.com/triggerdotdev" target="_blank">
              <TwitterIcon />
            </a>
          </li>
        </ol>
      </div>
    </header>
  );
}


================================================
FILE: app/components/Home/HomeHeroSection.tsx
================================================
import { AutoplayVideo } from "../AutoplayVideo";
import { NewFile } from "../NewFile";
import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
import { SmallSubtitle } from "../Primitives/SmallSubtitle";

import heroVideo from "~/assets/home/JsonHero2.mp4";

const jsonHeroTitle = "JSON sucks.";
const jsonHeroSlogan = "But we're making it better.";

export function HomeHeroSection() {
  return (
    <div
      className={`flex items-stretch flex-col md:flex-row bg-[rgb(56,52,139)] lg:p-6 lg:pb-16 pt-20 lg:pt-32`}
    >
      <div className="self-center md:w-1/2 md:pr-10 flex justify-end">
        <div className=" max-w-3xl">
          <AutoplayVideo src={heroVideo} />
        </div>
      </div>
      <div className="self-center flex align-center md:w-1/2 px-6 pb-8 mt-8 lg:mt-0">
        <div className="max-w-lg">
          <ExtraLargeTitle className="text-lime-300">
            {jsonHeroTitle}
          </ExtraLargeTitle>
          <ExtraLargeTitle className="text-white mb-4">
            {jsonHeroSlogan}
          </ExtraLargeTitle>
          <SmallSubtitle className="text-slate-200 mb-8">
            Stop staring at thousand line JSON files in your editor and start
            staring at thousand line JSON files in the world's best JSON viewer.
            With a few nice features to help make it not <em>the worst</em>.
          </SmallSubtitle>
          <NewFile />
        </div>
      </div>
    </div>
  );
}


================================================
FILE: app/components/Home/HomeInfoBoxSection.tsx
================================================
import React, { useEffect, useRef, useState } from "react";
import { JsonProvider } from "~/hooks/useJson";
import { JsonColumnViewProvider, useJsonColumnViewAPI, } from "~/hooks/useJsonColumnView";
import { JsonDocProvider } from "~/hooks/useJsonDoc";
import { JsonPreview } from "../JsonPreview";
import { PreviewValue } from "../Preview/PreviewValue";
import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
import { SmallSubtitle } from "../Primitives/SmallSubtitle";
import { PropertiesValue } from "../Properties/PropertiesValue";
import { HomeSection } from "./HomeSection";

const json = {
  id: "a1c33bd1-0528-4de3-a745-44d95e7ac3d8",
  title: "JSON Hero is a tool for JSON",
  thumbnail: "https://media.giphy.com/media/13CoXDiaCcCoyk/giphy-downsized.gif",
  createdAt: "2022-02-01T02:25:41-05:00",
  tint: "#EAB308",
  webpages: "https://www.theonion.com/",
  youtube: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  json: "bourne",
};

const infoBoxData = [
  {
    title: "Images",
    highlight: "$.thumbnail",
  },
  {
    title: "Dates",
    highlight: "$.createdAt",
  },
  {
    title: "Colors",
    highlight: "$.tint",
  },
  {
    title: "URLs",
    highlight: "$.webpages",
  },
  {
    title: "Videos",
    highlight: "$.youtube",
  },
];

const autoplayDuration = 3000;

export function HomeInfoBoxSection() {
  return (
    <SampleJSONPreview initialSelection={infoBoxData[0].highlight}>
      <HomeInfoBoxSectionContent/>
    </SampleJSONPreview>
  );
}

function HomeInfoBoxSectionContent() {
  const [index, setIndex] = useState(0);
  const api = useJsonColumnViewAPI();
  const interval = useRef<NodeJS.Timer | null>(null);

  useEffect(() => {
    const selectedPath = infoBoxData[index].highlight;
    api.goToNodeId(selectedPath, "home");
  }, [index]);

  const resetInterval = () => {
    if (interval.current != null) {
      clearInterval(interval.current);
    }
    interval.current = setInterval(() => {
      setIndex((i) => (i = (i + 1) % infoBoxData.length));
    }, autoplayDuration);
  };

  useEffect(() => {
    resetInterval();
    return () => {
      if (interval.current == null) return;
      clearInterval(interval.current);
    };
  }, []);

  return (
    <HomeSection containerClassName="bg-black p-6">
      <div className="md:pr-4 lg:pr-10 flex flex-col w-full md:w-1/2">
        <ExtraLargeTitle className="text-white mb-4">
          <span className=" text-lime-300">{infoBoxData[index].title}</span> are
          more than just strings
        </ExtraLargeTitle>
        <SmallSubtitle className="text-slate-400 mb-10">
          We figure out what your strings are made of, so you don't have to.
        </SmallSubtitle>
        <ul className="flex w-full text-slate-300 mb-3">
          {infoBoxData.map((value, i) => {
            return (
              <li
                key={value.highlight}
                onClick={() => {
                  resetInterval();
                  setIndex(i);
                }}
                className={`flex flex-grow justify-center px-4 py-2 cursor-pointer border-b-2 ${
                  index === i
                    ? "text-white border-lime-500"
                    : "border-slate-600"
                }`}
              >
                {value.title}
              </li>
            );
          })}
        </ul>
        <div className="w-full">
          <JsonPreview
            json={json}
            highlightPath={infoBoxData[index].highlight}
          />
        </div>
      </div>
      <div className="relative w-full md:w-1/2 flex flex-col justify-center items-center py-5">
        <div className="pointer-events-none absolute z-10 bottom-0 w-full h-[200px] bg-gradient-to-t from-slate-900 to-transparent mb-5"></div>
        <div className="pointer-events-auto min-w-full max-w-full p-4 rounded-sm bg-slate-900 h-[65vh] overflow-y-auto custom-scrollbar">
          <div className="pointer-events-none">
            <div className="mb-4">
              <PreviewValue/>
            </div>
            <PropertiesValue/>
          </div>
        </div>
      </div>
    </HomeSection>
  );
}

function SampleJSONPreview({
  children,
  initialSelection,
}: {
  children: React.ReactNode;
  initialSelection: string;
}) {
  return (
    <JsonDocProvider
      doc={{
        id: "sample",
        title: "Sample",
        type: "raw",
        readOnly: false,
        contents: "",
      }}
      path={initialSelection}
    >
      <JsonProvider initialJson={json}>
        <JsonColumnViewProvider>{children}</JsonColumnViewProvider>
      </JsonProvider>
    </JsonDocProvider>
  );
}


================================================
FILE: app/components/Home/HomeSearchSection.tsx
================================================
import { AutoplayVideo } from "../AutoplayVideo";
import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
import { SmallSubtitle } from "../Primitives/SmallSubtitle";
import { HomeSection } from "./HomeSection";

import searchVideo from "~/assets/home/JsonHeroSearch.mp4";

export function HomeSearchSection() {
  return (
    <HomeSection containerClassName="py-10 px-6 bg-black md:py-36 lg:py-20">
      <div className="w-full md:pr-10 md:w-1/2">
        <ExtraLargeTitle className="text-white mb-4">
          Quickly search your whole JSON file
        </ExtraLargeTitle>
        <SmallSubtitle className="mb-6 md:mb-10">
          Search for absolutely anything in your JSON file with blistering
          speed. Use the fuzzy matching and keyboard shortcuts to make
          navigating your files even faster.
        </SmallSubtitle>
      </div>
      <div className="w-full md:w-1/2">
        <AutoplayVideo src={searchVideo} />
      </div>
    </HomeSection>
  );
}


================================================
FILE: app/components/Home/HomeSection.tsx
================================================
export type HomeSectionProps = {
  containerClassName?: string;
  maxWidth?: string;
  reversed?: boolean;
  flipped?: boolean;
  children: React.ReactNode;
};

export function HomeSection({
  containerClassName,
  maxWidth = "1150px",
  reversed = false,
  flipped = false,
  children,
}: HomeSectionProps) {
  return (
    <div className={`flex justify-center items-center ${containerClassName}`}>
      <div
        className={`flex flex-col md:flex-row w-full ${
          reversed ? "md:flex-row-reverse" : ""
        }${flipped ? "flex-col-reverse" : ""}`}
        style={{ maxWidth: maxWidth }}
      >
        {children}
      </div>
    </div>
  );
}


================================================
FILE: app/components/Home/HomeSplitSection.tsx
================================================
import React from "react";

export type HomeSplitSectionProps = {
  className?: string;
  children: React.ReactNode;
};

export function HomeSplitSection({
  className,
  children,
}: HomeSplitSectionProps) {
  return (
    <div
      className={`grid lg:grid-cols-2 items-center justify-items-center py-12 ${className}`}
    >
      {children}
    </div>
  );
}

export function HomeSplitTextContent({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="justify-self-center lg:justify-self-end max-w-2xl px-20 flex flex-col justify-center">
      {children}
    </div>
  );
}

export function HomeSplitMediaContent({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="flex justify-center items-center px-10 py-5">
      {children}
    </div>
  );
}


================================================
FILE: app/components/Icons/ArrayIcon.tsx
================================================
export function ArrayIcon(props: React.SVGProps<SVGSVGElement>) {

  return (
    <svg
    className={props.className}
      xmlns="http://www.w3.org/2000/svg"
      width="1em"
      height="1em"
      viewBox="0 0 24 24"
      key="array"
    >
     <path d="M5.8899 18.525C5.8899 17.2 4.80855 17.025 3.84224 17.025C3.52013 17.025 3.22104 17.05 2.89893 17.05V2.95C4.00329 2.95 5.8899 3.25 5.8899 1.475C5.8899 0.525001 5.19968 0 4.37141 0H2.04766C0.80526 0 0 0.700001 0 2.1V17.925C0 19.35 0.782252 20 2.04766 20H4.37141C5.19968 20 5.8899 19.475 5.8899 18.525Z" fill="currentColor"/>
     <path d="M20 17.925V2.1C20 0.700001 19.1947 0 17.9523 0H15.6286C14.8003 0 14.1101 0.525001 14.1101 1.475C14.1101 2.825 15.3065 2.975 16.2958 2.975C16.5489 2.975 16.825 2.95 17.1011 2.95V17.05C16.0197 17.05 14.1101 16.8 14.1101 18.525C14.1101 19.525 14.9154 20 15.7436 20H18.0904C19.3098 20 20 19.25 20 17.925Z" fill="currentColor"/>

    </svg>
  );
};



================================================
FILE: app/components/Icons/ArrowKeysIcon.tsx
================================================
export function ArrowKeysIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="62"
      height="14"
      viewBox="0 0 62 14"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <rect width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M6.60956 4.48804C6.80972 4.23784 7.19026 4.23784 7.39043 4.48804L10.3501 8.18765C10.612 8.51503 10.3789 9 9.95969 9H4.04031C3.62106 9 3.38797 8.51503 3.64988 8.18765L6.60956 4.48804Z"
        fill="black"
      />
      <rect x="16" width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M23.3904 9.51196C23.1903 9.76216 22.8097 9.76216 22.6096 9.51196L19.6499 5.81235C19.388 5.48496 19.6211 5 20.0403 5L25.9597 5C26.3789 5 26.612 5.48497 26.3501 5.81235L23.3904 9.51196Z"
        fill="black"
      />
      <rect x="32" width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M36.488 7.39044C36.2378 7.19028 36.2378 6.80974 36.488 6.60957L40.1877 3.64988C40.515 3.38797 41 3.62106 41 4.04031L41 9.95969C41 10.3789 40.515 10.612 40.1877 10.3501L36.488 7.39044Z"
        fill="black"
      />
      <rect x="48" width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M57.512 6.60956C57.7622 6.80972 57.7622 7.19026 57.512 7.39043L53.8123 10.3501C53.485 10.612 53 10.3789 53 9.95969L53 4.04031C53 3.62106 53.485 3.38797 53.8123 3.64988L57.512 6.60956Z"
        fill="black"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/ArrowKeysUpDownIcon.tsx
================================================
export function ArrowKeysUpDownIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="28"
      height="14"
      viewBox="0 0 30 14"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <rect width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M6.60956 4.48804C6.80972 4.23784 7.19026 4.23784 7.39043 4.48804L10.3501 8.18765C10.612 8.51503 10.3789 9 9.95969 9H4.04031C3.62106 9 3.38797 8.51503 3.64988 8.18765L6.60956 4.48804Z"
        fill="black"
      />
      <rect x="16" width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M23.3904 9.51196C23.1903 9.76216 22.8097 9.76216 22.6096 9.51196L19.6499 5.81235C19.388 5.48496 19.6211 5 20.0403 5L25.9597 5C26.3789 5 26.612 5.48497 26.3501 5.81235L23.3904 9.51196Z"
        fill="black"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/CopyShortcutIcon.tsx
================================================
export function CopyShortcutIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="30"
      height="14"
      viewBox="0 0 30 14"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <rect width="14" height="14" rx="1.53846" fill="currentColor" />
      <rect x="16" width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M5.64,10.22H8.25V7a.39.39,0,0,1,.38-.39H10l-3-3L4,6.65H5.26A.39.39,0,0,1,5.64,7v3.18Zm3,.78H5.26a.38.38,0,0,1-.39-.39V7.43H3.11a.39.39,0,0,1-.28-.66L6.72,2.86a.39.39,0,0,1,.55,0l3.88,3.91a.38.38,0,0,1-.27.66H9v3.18a.38.38,0,0,1-.39.39Z"
        stroke="#0f172a" strokeWidth="0.35px" fill="#0f172a"
      />
      <path
        d="M23.81,9.52a1.66,1.66,0,0,0,.53-.27,1.57,1.57,0,0,0,.35-.42,1.07,1.07,0,0,0,.13-.53h1.6a2.26,2.26,0,0,1-.25,1,2.87,2.87,0,0,1-.7.85,3.35,3.35,0,0,1-1,.58,3.56,3.56,0,0,1-1.22.21,3.73,3.73,0,0,1-1.55-.31,3.2,3.2,0,0,1-1.11-.84,3.61,3.61,0,0,1-.67-1.22,4.91,4.91,0,0,1-.23-1.5V6.88a4.91,4.91,0,0,1,.23-1.5,3.54,3.54,0,0,1,.67-1.22,3.33,3.33,0,0,1,1.11-.84,3.94,3.94,0,0,1,2.83-.09,3,3,0,0,1,1,.59,2.66,2.66,0,0,1,.67.92,2.94,2.94,0,0,1,.23,1.17h-1.6a1.48,1.48,0,0,0-.45-1.07,1.65,1.65,0,0,0-.52-.33,1.75,1.75,0,0,0-.65-.12,1.59,1.59,0,0,0-1.44.78,2.27,2.27,0,0,0-.31.8,4.53,4.53,0,0,0-.09.91v.24a4.63,4.63,0,0,0,.09.92,2.46,2.46,0,0,0,.3.8,1.67,1.67,0,0,0,.56.56,1.63,1.63,0,0,0,.89.22A2.05,2.05,0,0,0,23.81,9.52Z"
        fill="#0f172a"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/DiscordIcon.tsx
================================================
export function DiscordIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <circle cx="12" cy="12" r="12" fill="#F8FAFC" />
      <path
        d="M18.0881 7.3374C18.0116 7.27279 17.9402 7.2032 17.8637 7.14356C17.554 6.88097 17.2269 6.63856 16.8846 6.41792C16.4342 6.13677 15.9516 5.90824 15.4464 5.73702C15.0844 5.61277 14.7172 5.51835 14.35 5.40901C14.2837 5.40901 14.2786 5.38414 14.3092 5.3245C14.3398 5.26485 14.4061 5.14558 14.4469 5.05115C14.4538 5.03366 14.4667 5.0191 14.4835 5.01001C14.5003 5.00092 14.5198 4.99789 14.5387 5.00146C14.809 5.04619 15.0844 5.07601 15.3547 5.13069C15.8281 5.229 16.2896 5.3756 16.7316 5.56805C17.1998 5.76225 17.6502 5.99501 18.0779 6.26385C18.2267 6.353 18.3697 6.45094 18.5063 6.5571C18.5891 6.62989 18.6566 6.71764 18.7051 6.81552C19.1108 7.51363 19.4521 8.24546 19.7251 9.00236C20.1066 10.0234 20.3983 11.0742 20.5971 12.1435C20.7042 12.715 20.7909 13.2866 20.8674 13.8631C20.9184 14.216 20.9388 14.5788 20.9745 14.9366C20.9745 15.0559 20.9745 15.1702 21 15.2895C21 15.3164 20.9911 15.3425 20.9745 15.3641C20.462 15.9257 19.8549 16.398 19.1794 16.7606C18.5379 17.1017 17.8516 17.3558 17.1395 17.5161C16.7511 17.6096 16.3554 17.6711 15.9564 17.7H15.7116C15.701 17.7002 15.6904 17.6981 15.6807 17.6938C15.671 17.6895 15.6624 17.6831 15.6555 17.6752C15.4413 17.4068 15.2323 17.1334 15.0232 16.8551V16.8253C16.3606 16.3823 17.5548 15.6041 18.4859 14.5689C18.3788 14.6434 18.2819 14.718 18.1748 14.7826C17.8739 14.9665 17.5781 15.1504 17.267 15.3193C16.7354 15.61 16.1728 15.8433 15.5892 16.0151C14.6422 16.3069 13.6595 16.474 12.6671 16.5121H12.3713H11.8155C11.4011 16.5146 10.9871 16.4897 10.5762 16.4376C10.1887 16.3879 9.80109 16.3332 9.41351 16.2636C8.86661 16.1567 8.33068 16.002 7.81221 15.8014C7.15233 15.5479 6.523 15.2246 5.93553 14.8372L5.55306 14.5788C6.01711 15.0934 6.54864 15.5462 7.13396 15.9257C7.72153 16.3044 8.35541 16.6099 9.02084 16.8352L8.98514 16.8899L8.39358 17.6553C8.38145 17.6729 8.36453 17.6868 8.34472 17.6956C8.3249 17.7044 8.30298 17.7076 8.28138 17.705C7.93875 17.691 7.59775 17.6511 7.26145 17.5857C6.76756 17.4952 6.28289 17.3621 5.81314 17.1881C5.27458 16.9934 4.76114 16.7382 4.28323 16.4277C3.86783 16.1551 3.48621 15.8365 3.14601 15.4784C3.14601 15.4784 3.12051 15.4386 3.10011 15.4287C3.06012 15.3983 3.03012 15.3571 3.01381 15.3103C2.9975 15.2635 2.99559 15.2131 3.00831 15.1653L3.05421 14.6335C3.0899 14.2856 3.1205 13.9426 3.1664 13.5947C3.2123 13.2468 3.28879 12.7647 3.36529 12.3472C3.51174 11.5311 3.7093 10.7244 3.95685 9.93177C4.16738 9.2543 4.42116 8.59033 4.71671 7.94373C4.91624 7.50667 5.14275 7.08178 5.39497 6.6714C5.46939 6.5728 5.56514 6.49137 5.67544 6.43284C6.1388 6.11857 6.63239 5.84893 7.14925 5.62769C7.71444 5.38251 8.30641 5.20075 8.91375 5.08594L9.47981 5.00643C9.49599 5.00328 9.51279 5.00611 9.52694 5.01438C9.54108 5.02265 9.55155 5.03575 9.55631 5.05115L9.7042 5.33942C9.7297 5.38415 9.7042 5.39907 9.6685 5.40901C9.41351 5.47859 9.15854 5.54319 8.90865 5.61774C8.45618 5.75584 8.01886 5.93729 7.60313 6.15946C7.24627 6.34465 6.9052 6.5574 6.58319 6.79565C6.3588 6.9696 6.14462 7.14853 5.92533 7.32745C5.9235 7.33135 5.92255 7.33557 5.92255 7.33986C5.92255 7.34415 5.9235 7.3484 5.92533 7.35229L5.99163 7.32248C6.471 7.09882 6.95037 6.86522 7.43994 6.65647C8.00719 6.4106 8.59831 6.22081 9.20443 6.08991C9.61682 5.99062 10.0361 5.92083 10.459 5.88114C10.8414 5.84635 11.2239 5.82649 11.6013 5.80661C11.79 5.80661 11.9787 5.80661 12.1673 5.80661C12.5141 5.80661 12.866 5.8414 13.2128 5.86625C13.8437 5.91322 14.4686 6.01806 15.0793 6.17936C15.6332 6.32264 16.1739 6.51049 16.6959 6.74099L17.9606 7.33243L18.0218 7.36224L18.0881 7.3374ZM9.35232 10.5679C9.08643 10.5761 8.82881 10.66 8.6113 10.8093C8.39378 10.9586 8.2259 11.1667 8.12839 11.4079C7.98657 11.7022 7.93351 12.0296 7.97541 12.3522C8.01397 12.7406 8.19505 13.1024 8.48538 13.371C8.61754 13.5006 8.77761 13.6 8.95401 13.6619C9.13041 13.7238 9.31872 13.7467 9.50531 13.7289C9.68475 13.7178 9.85988 13.6705 10.0196 13.5901C10.1794 13.5097 10.3203 13.3979 10.4335 13.2617C10.7252 12.9245 10.8682 12.4886 10.8312 12.049C10.8196 11.7253 10.7096 11.4122 10.515 11.1494C10.3862 10.9659 10.2123 10.8166 10.0093 10.7151C9.80628 10.6135 9.58046 10.563 9.35232 10.5679ZM16.1094 12.1733C16.1148 11.8593 16.0319 11.55 15.8697 11.2787C15.7548 11.0583 15.5775 10.8747 15.3587 10.7496C15.14 10.6245 14.889 10.5632 14.6356 10.5729C14.451 10.578 14.2698 10.6219 14.1043 10.7017C13.9388 10.7815 13.793 10.8953 13.6769 11.0351C13.5285 11.203 13.4159 11.398 13.3459 11.6088C13.2758 11.8196 13.2496 12.0419 13.2689 12.2627C13.2861 12.6947 13.4787 13.1023 13.8043 13.3959C13.9417 13.5243 14.1072 13.6205 14.2883 13.6773C14.4694 13.7342 14.6614 13.7501 14.8498 13.7239C15.1962 13.6764 15.5095 13.4978 15.7218 13.2269C15.9694 12.9284 16.106 12.5571 16.1094 12.1733Z"
        fill="#4338CA"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/DiscordIconTransparent.tsx
================================================
export function DiscordIconTransparent(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM17.9988 7.2591C18.0282 7.28536 18.0577 7.31177 18.0881 7.3374L18.0218 7.36224L17.9606 7.33243L16.6959 6.74099C16.1739 6.51049 15.6332 6.32264 15.0793 6.17936C14.4686 6.01806 13.8437 5.91322 13.2128 5.86625C13.1375 5.86085 13.0619 5.85498 12.9862 5.84911C12.7134 5.82793 12.4388 5.80661 12.1673 5.80661H11.6013L11.5799 5.80773C11.2095 5.82724 10.8342 5.84701 10.459 5.88114C10.0361 5.92083 9.61682 5.99062 9.20443 6.08991C8.59831 6.22081 8.00719 6.4106 7.43994 6.65647C7.07334 6.81278 6.71245 6.98304 6.35301 7.15262C6.23244 7.2095 6.11204 7.2663 5.99163 7.32248L5.92533 7.35229C5.9235 7.3484 5.92255 7.34415 5.92255 7.33986C5.92255 7.33557 5.9235 7.33135 5.92533 7.32745C5.97774 7.28469 6.02985 7.24194 6.08188 7.19925C6.24757 7.0633 6.41242 6.92803 6.58319 6.79565C6.9052 6.5574 7.24627 6.34465 7.60313 6.15946C8.01886 5.93729 8.45618 5.75584 8.90865 5.61774C9.08216 5.56598 9.25813 5.51901 9.43486 5.47184C9.51264 5.45108 9.59057 5.43027 9.6685 5.40901C9.7042 5.39907 9.7297 5.38415 9.7042 5.33942L9.5563 5.05115C9.55155 5.03575 9.54108 5.02265 9.52694 5.01438C9.51279 5.00611 9.49599 5.00328 9.47981 5.00643L8.91375 5.08594C8.30641 5.20075 7.71444 5.38251 7.14925 5.62769C6.63239 5.84893 6.1388 6.11857 5.67544 6.43284C5.56514 6.49137 5.46939 6.5728 5.39497 6.6714C5.14275 7.08178 4.91624 7.50667 4.71671 7.94374C4.42116 8.59033 4.16738 9.2543 3.95685 9.93176C3.7093 10.7244 3.51174 11.5311 3.36529 12.3472C3.28879 12.7647 3.2123 13.2468 3.1664 13.5947C3.13139 13.8601 3.10529 14.1225 3.07903 14.3865C3.07086 14.4686 3.06268 14.5509 3.05421 14.6335L3.00831 15.1653C2.99559 15.2131 2.9975 15.2635 3.01381 15.3103C3.03012 15.3571 3.06012 15.3983 3.10011 15.4287C3.12051 15.4386 3.14601 15.4784 3.14601 15.4784C3.48621 15.8365 3.86783 16.1551 4.28323 16.4277C4.76114 16.7382 5.27458 16.9934 5.81314 17.1881C6.28289 17.3621 6.76756 17.4952 7.26145 17.5857C7.59775 17.6511 7.93875 17.691 8.28138 17.705C8.30297 17.7076 8.3249 17.7044 8.34472 17.6956C8.36453 17.6868 8.38145 17.6729 8.39358 17.6553L8.98514 16.8899L9.02084 16.8352C8.35542 16.6099 7.72153 16.3044 7.13396 15.9257C6.54864 15.5462 6.01711 15.0934 5.55306 14.5788L5.93553 14.8372C6.523 15.2246 7.15233 15.5479 7.81221 15.8014C8.33068 16.002 8.86661 16.1567 9.41352 16.2636C9.80109 16.3332 10.1887 16.3879 10.5762 16.4376C10.9871 16.4897 11.4011 16.5146 11.8155 16.5121H12.3713H12.6671C13.6595 16.474 14.6422 16.3069 15.5892 16.0151C16.1728 15.8433 16.7354 15.61 17.267 15.3193C17.5426 15.1696 17.8062 15.0082 18.0719 14.8455C18.1061 14.8245 18.1404 14.8036 18.1748 14.7826C18.25 14.7372 18.3202 14.6869 18.3924 14.6351C18.423 14.6132 18.454 14.591 18.4859 14.5689C17.5548 15.6041 16.3606 16.3823 15.0232 16.8253V16.8551C15.2323 17.1334 15.4413 17.4068 15.6555 17.6751C15.6624 17.6831 15.671 17.6895 15.6807 17.6938C15.6904 17.6981 15.701 17.7002 15.7116 17.7H15.9564C16.3554 17.6711 16.7511 17.6096 17.1395 17.5161C17.8516 17.3558 18.5379 17.1017 19.1794 16.7606C19.8549 16.398 20.462 15.9257 20.9745 15.3641C20.9911 15.3425 21 15.3164 21 15.2895C20.9745 15.1702 20.9745 15.0559 20.9745 14.9366C20.9626 14.8173 20.9524 14.6975 20.9422 14.5776C20.9218 14.338 20.9014 14.0983 20.8674 13.8631C20.7909 13.2866 20.7042 12.715 20.5971 12.1435C20.3983 11.0742 20.1066 10.0234 19.7251 9.00236C19.4521 8.24545 19.1108 7.51363 18.7051 6.81552C18.6566 6.71764 18.5891 6.62989 18.5063 6.5571C18.3697 6.45094 18.2267 6.353 18.0779 6.26385C17.6502 5.99501 17.1998 5.76225 16.7316 5.56805C16.2896 5.3756 15.8281 5.229 15.3547 5.13069C15.1869 5.09676 15.0172 5.0724 14.848 5.04811C14.7445 5.03326 14.6412 5.01843 14.5387 5.00146C14.5198 4.99789 14.5003 5.00092 14.4835 5.01001C14.4667 5.0191 14.4538 5.03366 14.4469 5.05115C14.4163 5.12207 14.3712 5.207 14.3378 5.27016C14.3267 5.2911 14.3168 5.30965 14.3092 5.3245C14.2786 5.38414 14.2837 5.40901 14.35 5.40901C14.4686 5.44432 14.5872 5.47808 14.7056 5.51178C14.9538 5.58245 15.2013 5.6529 15.4464 5.73702C15.9516 5.90824 16.4342 6.13677 16.8846 6.41792C17.2269 6.63856 17.554 6.88097 17.8637 7.14356C17.9098 7.17954 17.9541 7.21915 17.9988 7.2591ZM8.6113 10.8093C8.82881 10.66 9.08643 10.5761 9.35232 10.5679C9.58046 10.563 9.80628 10.6135 10.0093 10.7151C10.2123 10.8166 10.3862 10.9659 10.515 11.1494C10.7096 11.4122 10.8196 11.7253 10.8312 12.049C10.8682 12.4886 10.7252 12.9245 10.4335 13.2617C10.3203 13.3979 10.1794 13.5097 10.0196 13.5901C9.85988 13.6705 9.68475 13.7178 9.50531 13.7289C9.31872 13.7467 9.13041 13.7238 8.95401 13.6619C8.77761 13.6 8.61754 13.5006 8.48538 13.371C8.19505 13.1024 8.01397 12.7406 7.97541 12.3522C7.93351 12.0296 7.98657 11.7022 8.12839 11.4079C8.2259 11.1667 8.39378 10.9586 8.6113 10.8093ZM15.8697 11.2787C16.0319 11.55 16.1148 11.8593 16.1094 12.1733C16.106 12.5571 15.9694 12.9284 15.7218 13.2269C15.5095 13.4978 15.1962 13.6764 14.8498 13.7239C14.6614 13.7501 14.4694 13.7342 14.2883 13.6773C14.1072 13.6205 13.9417 13.5243 13.8043 13.3959C13.4787 13.1023 13.2861 12.6947 13.2689 12.2627C13.2496 12.0419 13.2758 11.8196 13.3459 11.6088C13.4159 11.398 13.5285 11.203 13.6769 11.0351C13.793 10.8953 13.9388 10.7815 14.1043 10.7017C14.2698 10.6219 14.451 10.578 14.6356 10.5729C14.889 10.5632 15.14 10.6245 15.3587 10.7496C15.5775 10.8747 15.7548 11.0583 15.8697 11.2787Z"
        fill="#F8FAFC"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/EmailIcon.tsx
================================================
export function EmailIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    
     <svg width="24" 
     height="24" 
     viewBox="0 0 24 24" 
     fill="none" 
     xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#4338CA"/>
<path d="M12 0C5.37251 0 0 5.37251 0 12C0 18.6275 5.37251 24 12 24C18.6275 24 24 18.6275 24 12C24 5.37251 18.6275 0 12 0ZM4.02864 7.38421C4.29607 6.6387 4.79768 6.14156 5.58914 5.98842C5.87824 5.9324 6.17928 5.91671 6.47473 5.91596C8.32732 5.90961 10.1799 5.91297 12.0329 5.91297C13.9553 5.91297 15.8781 5.90588 17.8005 5.91596C18.8852 5.92156 19.6614 6.44821 19.9732 7.33155C20.1017 7.69534 20.0629 7.93327 19.6685 8.12674C17.3475 9.26668 15.0508 10.4567 12.7134 11.5611C12.3287 11.743 11.709 11.7397 11.3232 11.5574C8.97087 10.4451 6.65774 9.24988 4.32296 8.09948C3.97634 7.92878 3.9136 7.70468 4.02864 7.38384V7.38421ZM20.0935 15.7821C20.089 17.1977 19.2277 18.0788 17.808 18.0825C13.9467 18.0926 10.085 18.0923 6.22373 18.0825C4.83466 18.0792 3.94572 17.2534 3.92032 15.9031C3.88708 14.1223 3.90986 12.3399 3.91397 10.5586C3.91397 10.4279 3.95879 10.2972 3.99502 10.0966C4.23406 10.2019 4.42268 10.2759 4.60346 10.3659C6.91658 11.52 9.23195 12.6708 11.5376 13.8395C11.8685 14.0072 12.1311 14.0083 12.4624 13.8403C14.7677 12.6716 17.0827 11.5212 19.3954 10.367C19.5777 10.2763 19.7671 10.1993 20.058 10.069C20.0741 10.3752 20.0924 10.5635 20.0928 10.7514C20.095 12.428 20.098 14.1051 20.0928 15.7817L20.0935 15.7821Z" fill="white"/>
</svg>
  );
}


================================================
FILE: app/components/Icons/EmailIconTransparent.tsx
================================================
export function EmailIconTransparent(props: React.SVGProps<SVGSVGElement>) {
  return (
    
      <svg width="24" 
      height="24" 
      viewBox="0 0 24 24" 
      fill="none" 
      xmlns="http://www.w3.org/2000/svg">
<path d="M12 0C5.37251 0 0 5.37251 0 12C0 18.6275 5.37251 24 12 24C18.6275 24 24 18.6275 24 12C24 5.37251 18.6275 0 12 0ZM4.02864 7.38421C4.29607 6.6387 4.79768 6.14156 5.58914 5.98842C5.87824 5.9324 6.17928 5.91671 6.47473 5.91596C8.32732 5.90961 10.1799 5.91297 12.0329 5.91297C13.9553 5.91297 15.8781 5.90588 17.8005 5.91596C18.8852 5.92156 19.6614 6.44821 19.9732 7.33155C20.1017 7.69534 20.0629 7.93327 19.6685 8.12674C17.3475 9.26668 15.0508 10.4567 12.7134 11.5611C12.3287 11.743 11.709 11.7397 11.3232 11.5574C8.97087 10.4451 6.65774 9.24988 4.32296 8.09948C3.97634 7.92878 3.9136 7.70468 4.02864 7.38384V7.38421ZM20.0935 15.7821C20.089 17.1977 19.2277 18.0788 17.808 18.0825C13.9467 18.0926 10.085 18.0923 6.22373 18.0825C4.83466 18.0792 3.94572 17.2534 3.92032 15.9031C3.88708 14.1223 3.90986 12.3399 3.91397 10.5586C3.91397 10.4279 3.95879 10.2972 3.99502 10.0966C4.23406 10.2019 4.42268 10.2759 4.60346 10.3659C6.91658 11.52 9.23195 12.6708 11.5376 13.8395C11.8685 14.0072 12.1311 14.0083 12.4624 13.8403C14.7677 12.6716 17.0827 11.5212 19.3954 10.367C19.5777 10.2763 19.7671 10.1993 20.058 10.069C20.0741 10.3752 20.0924 10.5635 20.0928 10.7514C20.095 12.428 20.098 14.1051 20.0928 15.7817L20.0935 15.7821Z" fill="white"/>
</svg>
  );
}


================================================
FILE: app/components/Icons/EscapeKeyIcon.tsx
================================================
export function EscapeKeyIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="14"
      height="14"
      viewBox="0 0 14 14"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <rect width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M3.21695 10C2.79876 10 2.42068 9.88168 2.08269 9.64504C1.75044 9.4084 1.48693 9.0687 1.29216 8.62595C1.09739 8.17557 1 7.63359 1 7C1 6.38168 1.09739 5.85114 1.29216 5.4084C1.49265 4.95802 1.75044 4.61069 2.06551 4.36641C2.38058 4.12214 2.71283 4 3.06228 4C3.48619 4 3.83563 4.12595 4.1106 4.37786C4.3913 4.62214 4.59753 4.9542 4.72928 5.37405C4.86677 5.79389 4.93551 6.25954 4.93551 6.77099C4.93551 6.93893 4.92692 7.10305 4.90973 7.26336C4.89827 7.41603 4.88682 7.52672 4.87536 7.59542H2.42641C2.49515 7.93893 2.61831 8.17939 2.7959 8.31679C2.97348 8.44656 3.18257 8.51145 3.42317 8.51145C3.69814 8.51145 3.9903 8.39695 4.29964 8.16794L4.78084 9.33588C4.5517 9.54962 4.29391 9.71374 4.00749 9.82824C3.72106 9.94275 3.45754 10 3.21695 10ZM2.40922 6.31298H3.68096C3.68096 6.0916 3.638 5.90076 3.55207 5.74046C3.47187 5.57252 3.32006 5.48855 3.09665 5.48855C2.93625 5.48855 2.79303 5.55344 2.66701 5.68321C2.54098 5.81298 2.45505 6.0229 2.40922 6.31298Z"
        fill="#0F172A"
      />
      <path
        d="M7.06968 10C6.79471 10 6.50256 9.92748 6.19322 9.78244C5.8896 9.62977 5.62609 9.43511 5.40268 9.19847L6.05573 7.98473C6.44527 8.36641 6.79471 8.55725 7.10405 8.55725C7.25872 8.55725 7.36757 8.53053 7.43058 8.4771C7.49932 8.41603 7.53369 8.32824 7.53369 8.21374C7.53369 8.06107 7.45063 7.94275 7.2845 7.85878C7.11838 7.76718 6.92647 7.66412 6.70879 7.54962C6.54266 7.45801 6.37653 7.34351 6.2104 7.20611C6.05 7.06107 5.91538 6.87786 5.80654 6.65649C5.6977 6.43511 5.64327 6.16794 5.64327 5.85496C5.64327 5.29008 5.80081 4.83969 6.11588 4.50382C6.43095 4.16794 6.84054 4 7.34465 4C7.69982 4 8.00344 4.08015 8.25549 4.24046C8.51328 4.39313 8.73669 4.56489 8.92573 4.75573L8.27268 5.92366C8.11801 5.77099 7.9662 5.65267 7.81726 5.5687C7.66832 5.48473 7.52797 5.44275 7.39621 5.44275C7.14415 5.44275 7.01813 5.54962 7.01813 5.76336C7.01813 5.9084 7.09546 6.0229 7.25013 6.10687C7.41053 6.18321 7.59671 6.27481 7.80866 6.38168C7.98052 6.46565 8.14951 6.57634 8.31564 6.71374C8.4875 6.85115 8.62785 7.03054 8.73669 7.25191C8.85126 7.47328 8.90855 7.75573 8.90855 8.09924C8.90855 8.63359 8.75101 9.08397 8.43594 9.45038C8.1266 9.81679 7.67118 10 7.06968 10Z"
        fill="#0F172A"
      />
      <path
        d="M11.5736 10C11.1669 10 10.8002 9.88168 10.4737 9.64504C10.1472 9.4084 9.88654 9.0687 9.69177 8.62595C9.50273 8.17557 9.4082 7.63359 9.4082 7C9.4082 6.36641 9.51418 5.82825 9.72614 5.3855C9.94382 4.93512 10.2274 4.5916 10.5768 4.35496C10.9263 4.11832 11.3044 4 11.7111 4C11.9689 4 12.2009 4.05343 12.4071 4.16031C12.6191 4.26718 12.8052 4.41221 12.9656 4.59542L12.2782 5.85496C12.1865 5.74809 12.1035 5.67557 12.029 5.6374C11.9545 5.59924 11.8772 5.58015 11.797 5.58015C11.522 5.58015 11.3072 5.70992 11.1525 5.96947C10.9979 6.22137 10.9205 6.56489 10.9205 7C10.9205 7.43511 11.0007 7.78244 11.1611 8.04198C11.3215 8.29389 11.5163 8.41985 11.7455 8.41985C11.8657 8.41985 11.9832 8.3855 12.0978 8.31679C12.2181 8.24046 12.3298 8.15267 12.4329 8.05344L13 9.33588C12.788 9.58779 12.5532 9.76336 12.2954 9.8626C12.0376 9.9542 11.797 10 11.5736 10Z"
        fill="#0F172A"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/GithubIcon.tsx
================================================
export function GithubIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <circle cx="12" cy="12" r="12" fill="#F8FAFC" />
      <path
        d="M21 12.0523C20.9915 12.1541 20.982 12.2565 20.9741 12.3588C20.8837 13.6147 20.5087 14.8358 19.8759 15.935C19.1892 17.1392 18.2177 18.1681 17.0412 18.9371C16.376 19.3775 15.6509 19.7259 14.8867 19.9725C14.6521 20.0485 14.4508 19.9604 14.2906 19.7886C14.1508 19.642 14.0747 19.4487 14.0779 19.249C14.0779 18.3672 14.0779 17.4853 14.0779 16.6031C14.0863 16.3681 14.0628 16.1329 14.008 15.9038C13.9792 15.7993 13.9324 15.6997 13.8918 15.5924C14.0311 15.5634 14.1766 15.5377 14.3216 15.5032C15.1958 15.3068 16.0136 14.9915 16.7231 14.4432C17.5308 13.8177 18.0294 13.0111 18.176 12.0157C18.3187 11.0531 18.2037 10.1195 17.7728 9.233C17.6063 8.89501 17.3874 8.58384 17.1236 8.31036L17.0903 8.2737C17.3809 7.63017 17.4174 6.90541 17.193 6.23744C17.1338 6.05812 17.0463 5.88882 16.9335 5.73562C16.9232 5.71873 16.9081 5.70507 16.89 5.69624C16.8719 5.68742 16.8516 5.6838 16.8314 5.68583C16.1357 5.70242 15.4598 5.91436 14.8856 6.29599C14.6578 6.44107 14.4484 6.61171 14.2618 6.80437C14.253 6.8169 14.2399 6.82597 14.2248 6.82998C14.2097 6.83399 14.1936 6.83267 14.1795 6.82626C13.6814 6.65717 13.1643 6.58603 12.6409 6.54936C12.1713 6.51543 11.6997 6.51927 11.2308 6.56085C10.7502 6.59484 10.2762 6.68958 9.82082 6.84268C9.80583 6.84985 9.78867 6.85153 9.77251 6.84741C9.75634 6.84328 9.74225 6.83364 9.73283 6.82024C9.20278 6.26705 8.50548 5.89131 7.74132 5.74712C7.55238 5.711 7.35723 5.70771 7.16491 5.68801C7.14583 5.6851 7.1263 5.68795 7.10894 5.69617C7.09159 5.7044 7.07726 5.71759 7.0679 5.73398C6.87448 6.00312 6.75032 6.3133 6.70581 6.63856C6.63122 7.07823 6.66761 7.52889 6.81184 7.95192C6.85301 8.06958 6.91505 8.18012 6.97145 8.3027C6.90772 8.37329 6.83158 8.45374 6.75939 8.53746C6.25302 9.13009 5.92997 9.84975 5.82765 10.6131C5.72834 11.2252 5.75886 11.8505 5.91733 12.4507C6.1621 13.3378 6.6872 14.0377 7.44973 14.5713C8.04589 14.9899 8.71085 15.2624 9.41924 15.4408C9.65331 15.4999 9.88963 15.5503 10.1231 15.6012C10.0859 15.7046 10.0408 15.8086 10.0103 15.9175C9.94909 16.1717 9.92142 16.4324 9.92798 16.6934C9.93155 16.7171 9.92539 16.7411 9.91082 16.7604C9.89625 16.7796 9.87445 16.7925 9.85015 16.7963C9.51067 16.9011 9.15221 16.935 8.79827 16.8959C8.46398 16.8686 8.14004 16.7699 7.84961 16.607C7.58186 16.4549 7.35663 16.2415 7.19367 15.9853C6.92069 15.5618 6.54507 15.269 6.03915 15.1481C5.87828 15.104 5.70835 15.1016 5.54621 15.1409C5.51726 15.1481 5.48911 15.158 5.46217 15.1705C5.38152 15.2104 5.35557 15.2756 5.40577 15.3472C5.45449 15.4143 5.5113 15.4755 5.57497 15.5295C5.70582 15.6389 5.85359 15.7374 5.97429 15.8578C6.24219 16.126 6.4255 16.4505 6.5727 16.793C6.79548 17.3091 7.18352 17.6686 7.68661 17.9209C8.20945 18.1819 8.77007 18.2443 9.34762 18.1945C9.53769 18.1775 9.72663 18.1453 9.91896 18.1201C9.91896 18.1305 9.92234 18.1458 9.92234 18.1606C9.92234 18.5321 9.92572 18.9037 9.92234 19.2753C9.92243 19.3969 9.89197 19.5168 9.8336 19.6244C9.77522 19.7321 9.69069 19.8243 9.58732 19.8931C9.51652 19.946 9.4329 19.9803 9.34451 19.9927C9.25611 20.0052 9.1659 19.9954 9.08253 19.9643C6.95171 19.2321 5.31609 17.9187 4.17567 16.0242C3.63235 15.1126 3.27001 14.1103 3.10744 13.0691C2.81135 11.2274 3.13103 9.3421 4.01965 7.68951C4.90826 6.03693 6.31908 4.70396 8.04532 3.88597C8.87822 3.48473 9.7727 3.21731 10.6939 3.09412C10.9951 3.05418 11.2991 3.0394 11.602 3.00985C11.6251 3.00985 11.6471 3.00328 11.6702 3H12.3346C12.3572 3.00328 12.3792 3.00766 12.4023 3.00985C12.6685 3.03283 12.9353 3.04761 13.2003 3.0799C14.0888 3.18707 14.9544 3.42898 15.7655 3.79677C17.2635 4.46177 18.5425 5.5162 19.4608 6.84323C20.3464 8.10221 20.8692 9.56789 20.9752 11.0887C20.9831 11.1905 20.9914 11.2926 21 11.3951V12.0523Z"
        fill="#4338CA"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/GithubIconSimple.tsx
================================================
export type IconProps = {
  className?: string;
};

export function GithubIconSimple({ className }: IconProps) {
  return (
    <svg
      className={className}
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="currentColor"
      xmlns="http://www.w3.org/2000/svg"
    >
      <g clipPath="url(#clip0_571_3822)">
        <path
          fillRule="evenodd"
          clipRule="evenodd"
          d="M12 0C5.37017 0 0 5.50708 0 12.306C0 17.745 3.44015 22.3532 8.20626 23.9849C8.80295 24.0982 9.02394 23.7205 9.02394 23.3881C9.02394 23.0935 9.01657 22.3229 9.00921 21.2956C5.67219 22.0359 4.96501 19.6487 4.96501 19.6487C4.41989 18.2285 3.63168 17.8508 3.63168 17.8508C2.54144 17.0878 3.71271 17.1029 3.71271 17.1029C4.91344 17.1936 5.55433 18.372 5.55433 18.372C6.62247 20.2531 8.36096 19.7092 9.04604 19.3919C9.15654 18.5987 9.46593 18.0548 9.80479 17.745C7.13812 17.4353 4.33886 16.3777 4.33886 11.6638C4.33886 10.3192 4.80295 9.2238 5.57643 8.36261C5.4512 8.05288 5.03867 6.79887 5.69429 5.1067C5.69429 5.1067 6.7035 4.77432 8.99447 6.36827C9.95212 6.09632 10.9761 5.96034 12 5.95279C13.0166 5.95279 14.0479 6.09632 15.0055 6.36827C17.2965 4.77432 18.3057 5.1067 18.3057 5.1067C18.9613 6.79887 18.5488 8.05288 18.4236 8.36261C19.1897 9.2238 19.6538 10.3192 19.6538 11.6638C19.6538 16.3928 16.8471 17.4278 14.1731 17.7375C14.6004 18.1152 14.9908 18.8706 14.9908 20.0189C14.9908 21.6657 14.9761 22.9877 14.9761 23.3957C14.9761 23.728 15.1897 24.1058 15.8011 23.9849C20.5672 22.3532 24 17.745 24 12.3135C24 5.50708 18.6298 0 12 0Z"
          fill="currentColor"
        />
      </g>
      <defs>
        <clipPath id="clip0_571_3822">
          <rect width="24" height="24" fill="white" />
        </clipPath>
      </defs>
    </svg>
  );
}


================================================
FILE: app/components/Icons/LoadingIcon.tsx
================================================
export function LoadingIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      xmlns="http://www.w3.org/2000/svg"
      width="26"
      height="26"
      viewBox="0 0 26 26"
      fill="none"
    >
      <circle
        cx="13"
        cy="13"
        r="10"
        stroke="black"
        strokeOpacity="0.3"
        strokeWidth="4"
      />
      <path
        d="M13 23C7.47715 23 3 18.5228 3 13"
        stroke="#4338CA"
        strokeWidth="4"
        strokeLinecap="round"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/Logo.tsx
================================================
import { Link } from "remix";

export function Logo({
  className,
  width = "100%",
}: {
  className?: string;
  width?: string;
}) {
  return (
    <Link to="/" aria-label="JSON Hero homepage" className="w-40">
      <svg
        className={className}
        width={width}
        height="50"
        viewBox="0 0 263 36"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M94.8087 35.3033V1.39929H102.661L111.501 18.2473L114.829 25.7353H115.037C114.898 23.9326 114.707 21.922 114.465 19.7033C114.222 17.4846 114.101 15.37 114.101 13.3593V1.39929H121.381V35.3033H113.529L104.689 18.4033L101.361 11.0193H101.153C101.326 12.8913 101.517 14.902 101.725 17.0513C101.967 19.2006 102.089 21.2806 102.089 23.2913V35.3033H94.8087Z"
          fill="white"
        />
        <path
          d="M73.0419 35.9273C69.9912 35.9273 67.3045 35.2166 64.9819 33.7953C62.6939 32.3739 60.8912 30.3459 59.5739 27.7113C58.2912 25.0419 57.6499 21.8699 57.6499 18.1953C57.6499 14.4859 58.2912 11.3486 59.5739 8.78327C60.8912 6.18327 62.6939 4.20727 64.9819 2.85527C67.3045 1.4686 69.9912 0.775269 73.0419 0.775269C76.0925 0.775269 78.7619 1.4686 81.0499 2.85527C83.3725 4.20727 85.1752 6.18327 86.4579 8.78327C87.7752 11.3833 88.4339 14.5206 88.4339 18.1953C88.4339 21.8699 87.7752 25.0419 86.4579 27.7113C85.1752 30.3459 83.3725 32.3739 81.0499 33.7953C78.7619 35.2166 76.0925 35.9273 73.0419 35.9273ZM73.0419 29.3233C75.3645 29.3233 77.2019 28.3179 78.5539 26.3073C79.9059 24.2966 80.5819 21.5926 80.5819 18.1953C80.5819 14.7979 79.9059 12.1459 78.5539 10.2393C77.2019 8.3326 75.3645 7.37927 73.0419 7.37927C70.7192 7.37927 68.8819 8.3326 67.5299 10.2393C66.1779 12.1459 65.5019 14.7979 65.5019 18.1953C65.5019 21.5926 66.1779 24.2966 67.5299 26.3073C68.8819 28.3179 70.7192 29.3233 73.0419 29.3233Z"
          fill="white"
        />
        <path
          d="M40.7154 35.9273C38.4967 35.9273 36.278 35.5113 34.0593 34.6793C31.8753 33.8473 29.9167 32.6339 28.1833 31.0393L32.5513 25.7873C33.7647 26.8273 35.1167 27.6766 36.6073 28.3353C38.098 28.9939 39.5367 29.3233 40.9234 29.3233C42.518 29.3233 43.6967 29.0286 44.4594 28.4393C45.2567 27.8499 45.6553 27.0526 45.6553 26.0473C45.6553 24.9726 45.2047 24.1926 44.3034 23.7073C43.4367 23.1873 42.258 22.6153 40.7673 21.9913L36.3474 20.1193C35.2034 19.6339 34.1114 18.9926 33.0714 18.1953C32.0314 17.3633 31.182 16.3406 30.5233 15.1273C29.8647 13.9139 29.5354 12.4926 29.5354 10.8633C29.5354 8.99127 30.038 7.2926 31.0434 5.76727C32.0834 4.24193 33.5047 3.0286 35.3074 2.12727C37.1447 1.22594 39.242 0.775269 41.5993 0.775269C43.5407 0.775269 45.482 1.1566 47.4234 1.91927C49.3647 2.68194 51.0634 3.79127 52.5194 5.24727L48.6194 10.0833C47.51 9.2166 46.4007 8.55794 45.2914 8.10727C44.182 7.62194 42.9514 7.37927 41.5993 7.37927C40.282 7.37927 39.2247 7.6566 38.4273 8.21127C37.6647 8.73127 37.2834 9.4766 37.2834 10.4473C37.2834 11.4873 37.7687 12.2673 38.7393 12.7873C39.7447 13.3073 40.9754 13.8619 42.4314 14.4513L46.7994 16.2193C48.8447 17.0513 50.474 18.1953 51.6874 19.6513C52.9007 21.1073 53.5074 23.0313 53.5074 25.4233C53.5074 27.2953 53.0047 29.0286 51.9994 30.6233C50.994 32.2179 49.538 33.5006 47.6314 34.4713C45.7247 35.4419 43.4194 35.9273 40.7154 35.9273Z"
          fill="white"
        />
        <path
          d="M11.6583 35.9273C9.09298 35.9273 6.92631 35.4246 5.15831 34.4193C3.39031 33.3793 1.91698 31.8366 0.738312 29.7913L5.93831 25.9433C6.56231 27.0873 7.29031 27.9366 8.12231 28.4913C8.95431 29.046 9.80365 29.3233 10.6703 29.3233C12.057 29.3233 13.097 28.9073 13.7903 28.0753C14.5183 27.2086 14.8823 25.6486 14.8823 23.3953V1.39929H22.5263V24.0193C22.5263 26.2033 22.145 28.1966 21.3823 29.9993C20.6196 31.802 19.4236 33.2406 17.7943 34.3153C16.1996 35.39 14.1543 35.9273 11.6583 35.9273Z"
          fill="white"
        />
        <path
          d="M247.108 35.9273C244.058 35.9273 241.371 35.2166 239.048 33.7953C236.76 32.3739 234.958 30.3459 233.64 27.7113C232.358 25.0419 231.716 21.8699 231.716 18.1953C231.716 14.4859 232.358 11.3486 233.64 8.78327C234.958 6.18327 236.76 4.20727 239.048 2.85527C241.371 1.4686 244.058 0.775269 247.108 0.775269C250.159 0.775269 252.828 1.4686 255.116 2.85527C257.439 4.20727 259.242 6.18327 260.524 8.78327C261.842 11.3833 262.5 14.5206 262.5 18.1953C262.5 21.8699 261.842 25.0419 260.524 27.7113C259.242 30.3459 257.439 32.3739 255.116 33.7953C252.828 35.2166 250.159 35.9273 247.108 35.9273ZM247.108 29.3233C249.431 29.3233 251.268 28.3179 252.62 26.3073C253.972 24.2966 254.648 21.5926 254.648 18.1953C254.648 14.7979 253.972 12.1459 252.62 10.2393C251.268 8.3326 249.431 7.37927 247.108 7.37927C244.786 7.37927 242.948 8.3326 241.596 10.2393C240.244 12.1459 239.568 14.7979 239.568 18.1953C239.568 21.5926 240.244 24.2966 241.596 26.3073C242.948 28.3179 244.786 29.3233 247.108 29.3233Z"
          fill="#BFF164"
        />
        <path
          d="M201.438 35.3033V1.39929H213.658C216.05 1.39929 218.234 1.72863 220.21 2.38729C222.186 3.01129 223.763 4.08596 224.942 5.61129C226.12 7.13663 226.71 9.25129 226.71 11.9553C226.71 14.4513 226.155 16.514 225.046 18.1433C223.971 19.738 222.515 20.934 220.678 21.7313L228.374 35.3033H219.794L213.294 23.0833H209.082V35.3033H201.438ZM209.082 16.9993H213.034C215.044 16.9993 216.57 16.5833 217.61 15.7513C218.684 14.8846 219.222 13.6193 219.222 11.9553C219.222 10.2913 218.684 9.12996 217.61 8.47129C216.57 7.81263 215.044 7.48329 213.034 7.48329H209.082V16.9993Z"
          fill="#BFF164"
        />
        <path
          d="M172.949 35.3033V1.39929H194.165V7.84729H180.593V14.6593H192.137V21.0553H180.593V28.8553H194.685V35.3033H172.949Z"
          fill="#BFF164"
        />
        <path
          d="M137.91 35.3033V1.39929H145.554V14.4513H157.254V1.39929H164.95V35.3033H157.254V21.1593H145.554V35.3033H137.91Z"
          fill="#BFF164"
        />
      </svg>
    </Link>
  );
}


================================================
FILE: app/components/Icons/LogoTriggerdotdev.tsx
================================================
export function LogoTriggerdotdev({
  className,
  width = "100%",
}: {
  className?: string;
  width?: string;
}) {
  return (
    <a href="https://trigger.dev/" aria-label="Trigger.dev">
      <svg
        className={`${className}`}
        width={width}
        height="30"
        viewBox="0 0 169 30"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M44.0084 4.04088H30.6671H31.1941V7.67807H35.686V23.329H39.489V7.67807H44.0084V4.04088Z"
          fill="#E2E8F0"
        />
        <path
          d="M47.646 11.9215V9.55178H44.0911V23.329H47.646V16.7435C47.646 13.8503 49.9884 13.0236 51.8348 13.2441V9.27623C50.0986 9.27623 48.3625 10.0478 47.646 11.9215Z"
          fill="#E2E8F0"
        />
        <path
          d="M55.6379 7.89851C56.8505 7.89851 57.8426 6.90655 57.8426 5.7217C57.8426 4.53686 56.8505 3.51733 55.6379 3.51733C54.453 3.51733 53.4609 4.53686 53.4609 5.7217C53.4609 6.90655 54.453 7.89851 55.6379 7.89851ZM53.8743 23.329H57.4292V9.55178H53.8743V23.329Z"
          fill="#E2E8F0"
        />
        <path
          d="M70.9327 9.55179V11.2602C69.9681 9.96509 68.48 9.16603 66.5234 9.16603C62.6103 9.16603 59.6616 12.3623 59.6616 16.22C59.6616 20.1052 62.6103 23.2739 66.5234 23.2739C68.48 23.2739 69.9681 22.4749 70.9327 21.1798V22.6677C70.9327 24.8445 69.5548 26.0569 67.3226 26.0569C65.2007 26.0569 64.2913 25.2027 63.7126 24.1281L60.6812 25.864C61.8938 28.096 64.2637 29.2257 67.2124 29.2257C70.85 29.2257 74.4049 27.1867 74.4049 22.6677V9.55179H70.9327ZM67.0746 19.9949C64.8424 19.9949 63.2165 18.4243 63.2165 16.22C63.2165 14.0432 64.8424 12.4726 67.0746 12.4726C69.3068 12.4726 70.9327 14.0432 70.9327 16.22C70.9327 18.4243 69.3068 19.9949 67.0746 19.9949Z"
          fill="#E2E8F0"
        />
        <path
          d="M87.8808 9.55179V11.2602C86.9163 9.96509 85.4282 9.16603 83.4716 9.16603C79.5584 9.16603 76.6097 12.3623 76.6097 16.22C76.6097 20.1052 79.5584 23.2739 83.4716 23.2739C85.4282 23.2739 86.9163 22.4749 87.8808 21.1798V22.6677C87.8808 24.8445 86.5029 26.0569 84.2708 26.0569C82.1488 26.0569 81.2394 25.2027 80.6607 24.1281L77.6294 25.864C78.8419 28.096 81.2119 29.2257 84.1605 29.2257C87.7981 29.2257 91.3531 27.1867 91.3531 22.6677V9.55179H87.8808ZM84.0227 19.9949C81.7906 19.9949 80.1647 18.4243 80.1647 16.22C80.1647 14.0432 81.7906 12.4726 84.0227 12.4726C86.2549 12.4726 87.8808 14.0432 87.8808 16.22C87.8808 18.4243 86.2549 19.9949 84.0227 19.9949Z"
          fill="#E2E8F0"
        />
        <path
          d="M97.2782 17.9008H107.667C107.75 17.4324 107.805 16.964 107.805 16.4404C107.805 12.3899 104.912 9.16603 100.833 9.16603C96.5066 9.16603 93.5579 12.3348 93.5579 16.4404C93.5579 20.546 96.479 23.7148 101.109 23.7148C103.754 23.7148 105.821 22.6402 107.116 20.7665L104.25 19.1132C103.644 19.9123 102.542 20.4909 101.164 20.4909C99.2899 20.4909 97.7742 19.7194 97.2782 17.9008ZM97.2231 15.1454C97.6364 13.3819 98.9316 12.3623 100.833 12.3623C102.321 12.3623 103.809 13.1614 104.25 15.1454H97.2231Z"
          fill="#E2E8F0"
        />
        <path
          d="M113.468 11.9215V9.55178H109.914V23.329H113.468V16.7435C113.468 13.8503 115.811 13.0236 117.657 13.2441V9.27623C115.921 9.27623 114.185 10.0478 113.468 11.9215Z"
          fill="#E2E8F0"
        />
        <path
          d="M119.008 23.6874C120.303 23.6874 121.35 22.6403 121.35 21.3452C121.35 20.0502 120.303 19.0031 119.008 19.0031C117.712 19.0031 116.665 20.0502 116.665 21.3452C116.665 22.6403 117.712 23.6874 119.008 23.6874Z"
          fill="#E2E8F0"
        />
        <path
          d="M133.944 4.04102V11.1776C132.952 9.91011 131.491 9.16616 129.479 9.16616C125.787 9.16616 122.755 12.3349 122.755 16.4405C122.755 20.5462 125.787 23.7149 129.479 23.7149C131.491 23.7149 132.952 22.9709 133.944 21.7034V23.3292H137.499V4.04102L133.944 4.04102ZM130.141 20.3257C127.936 20.3257 126.31 18.7551 126.31 16.4405C126.31 14.126 127.936 12.5553 130.141 12.5553C132.318 12.5553 133.944 14.126 133.944 16.4405C133.944 18.7551 132.318 20.3257 130.141 20.3257Z"
          fill="#E2E8F0"
        />
        <path
          d="M143.203 17.9009H153.592C153.675 17.4325 153.73 16.9641 153.73 16.4406C153.73 12.39 150.837 9.16617 146.758 9.16617C142.432 9.16617 139.483 12.3349 139.483 16.4406C139.483 20.5462 142.404 23.7149 147.034 23.7149C149.679 23.7149 151.746 22.6403 153.041 20.7666L150.175 19.1133C149.569 19.9124 148.467 20.4911 147.089 20.4911C145.215 20.4911 143.699 19.7195 143.203 17.9009ZM143.148 15.1455C143.561 13.382 144.857 12.3625 146.758 12.3625C148.246 12.3625 149.734 13.1616 150.175 15.1455H143.148Z"
          fill="#E2E8F0"
        />
        <path
          d="M164.45 9.55192L161.088 19.196L157.754 9.55192H153.84L159.076 23.3292H163.127L168.363 9.55192H164.45Z"
          fill="#E2E8F0"
        />
        <path
          fill-rule="evenodd"
          clip-rule="evenodd"
          d="M8.32238 9.89169L13.6403 0.682007L26.8195 23.5069H0.461029L5.77893 14.2969L9.54072 16.4686L7.9849 19.1632H19.2957L13.6403 9.3691L12.0845 12.0637L8.32238 9.89169Z"
          fill="#E2E8F0"
        />
      </svg>
    </a>
  );
}


================================================
FILE: app/components/Icons/MoonIcon.tsx
================================================
import { motion } from "framer-motion";
import { transition } from "../../utilities/animationConstants";

export const MoonIcon = () => {
  const variants = {
    initial: { scale: 0.6, rotate: 90 },
    animate: { scale: 1, rotate: 0, transition },
    whileTap: { scale: 0.95, rotate: 15 },
  };

  return (
    <motion.svg
      xmlns="http://www.w3.org/2000/svg"
      width="1em"
      height="1em"
      viewBox="0 0 50 50"
      key="moon"
    >
      <motion.path
        d="M 43.81 29.354 C 43.688 28.958 43.413 28.626 43.046 28.432 C 42.679 28.238 42.251 28.198 41.854 28.321 C 36.161 29.886 30.067 28.272 25.894 24.096 C 21.722 19.92 20.113 13.824 21.683 8.133 C 21.848 7.582 21.697 6.985 21.29 6.578 C 20.884 6.172 20.287 6.022 19.736 6.187 C 10.659 8.728 4.691 17.389 5.55 26.776 C 6.408 36.163 13.847 43.598 23.235 44.451 C 32.622 45.304 41.28 39.332 43.816 30.253 C 43.902 29.96 43.9 29.647 43.81 29.354 Z"
        fill="currentColor"
        initial="initial"
        animate="animate"
        whileTap="whileTap"
        variants={variants}
      />
    </motion.svg>
  );
};


================================================
FILE: app/components/Icons/ObjectIcon.tsx
================================================
export function ObjectIcon(props: React.SVGProps<SVGSVGElement>) {

  return (
    <svg
    className={props.className}
      xmlns="http://www.w3.org/2000/svg"
      width="1em"
      height="1em"
      viewBox="0 0 24 24"
      key="object"
    >
      <path d="M8.63302 22C7.03731 21.9857 5.84052 21.4581 5.04267 20.4171C4.24481 19.3761 3.84589 17.836 3.84589 15.7968C3.84589 15.1408 3.75799 14.6132 3.58219 14.2139C3.40639 13.8004 3.12241 13.4724 2.73024 13.2299L2.5274 13.123C2.33807 13.0232 2.20284 12.9091 2.12171 12.7807C2.04057 12.6524 2 12.4884 2 12.2888V11.6898C2 11.4902 2.04057 11.3262 2.12171 11.1979C2.20284 11.0695 2.33807 10.9554 2.5274 10.8556L2.75052 10.7273C3.14269 10.4848 3.41991 10.164 3.58219 9.76471C3.75799 9.36542 3.84589 8.83779 3.84589 8.18182C3.84589 6.1426 4.24481 4.60963 5.04267 3.58289C5.84052 2.54189 7.03055 2.01426 8.61273 2H8.73444C8.85615 2 8.95757 2.04278 9.03871 2.12834C9.13337 2.2139 9.1807 2.32799 9.1807 2.47059V3.86096C9.1807 3.98931 9.14013 4.10339 9.05899 4.20321C8.97785 4.28877 8.87643 4.33155 8.75473 4.33155H8.67359C8.06505 4.37433 7.59175 4.53832 7.25368 4.82353C6.9156 5.10873 6.67895 5.5508 6.54372 6.14973C6.40849 6.7344 6.34087 7.53298 6.34087 8.54546C6.34087 9.40107 6.20564 10.1283 5.93518 10.7273C5.67825 11.3119 5.31313 11.7326 4.83982 11.9893C5.31313 12.2317 5.67825 12.6595 5.93518 13.2727C6.20564 13.8717 6.34087 14.5918 6.34087 15.4332C6.34087 16.4456 6.40849 17.2513 6.54372 17.8503C6.67895 18.4349 6.9156 18.8699 7.25368 19.1551C7.59175 19.4545 8.05829 19.6185 8.6533 19.6471H8.75473C8.87643 19.6613 8.97785 19.7112 9.05899 19.7968C9.14013 19.8824 9.1807 19.9893 9.1807 20.1176V21.5294C9.1807 21.6578 9.13337 21.7647 9.03871 21.8503C8.95757 21.9501 8.85615 22 8.73444 22H8.63302Z" fill="currentColor"/>
      <path d="M15.367 2C16.9627 2.01426 18.1595 2.54189 18.9573 3.58289C19.7552 4.62389 20.1541 6.16399 20.1541 8.20321C20.1541 8.85918 20.242 9.39394 20.4178 9.80749C20.5936 10.2068 20.8776 10.5205 21.2698 10.7487L21.4726 10.8556C21.6619 10.9697 21.7972 11.0909 21.8783 11.2193C21.9594 11.3476 22 11.5045 22 11.6898V12.3102C22 12.4955 21.9594 12.6524 21.8783 12.7807C21.7972 12.9091 21.6619 13.0303 21.4726 13.1444L21.2495 13.2513C20.8708 13.4938 20.5936 13.8217 20.4178 14.2353C20.242 14.6346 20.1541 15.1622 20.1541 15.8182C20.1541 19.8966 18.5652 21.9572 15.3873 22H15.2656C15.1439 22 15.0357 21.9501 14.941 21.8503C14.8599 21.7647 14.8193 21.6578 14.8193 21.5294V20.1176C14.8193 19.9893 14.8599 19.8824 14.941 19.7968C15.0221 19.7112 15.1236 19.6613 15.2453 19.6471H15.3264C15.9349 19.6185 16.4083 19.4617 16.7463 19.1765C17.0844 18.8913 17.3211 18.4563 17.4563 17.8717C17.5915 17.2727 17.6591 16.467 17.6591 15.4545C17.6591 14.5847 17.7876 13.8574 18.0445 13.2727C18.315 12.6881 18.6869 12.2674 19.1602 12.0107C18.6869 11.754 18.315 11.3262 18.0445 10.7273C17.7876 10.1141 17.6591 9.38681 17.6591 8.54546C17.6591 7.53298 17.5915 6.7344 17.4563 6.14973C17.3211 5.5508 17.0844 5.10873 16.7463 4.82353C16.4083 4.53832 15.9417 4.38146 15.3467 4.35294L15.2453 4.33155C15.1371 4.33155 15.0357 4.28877 14.941 4.20321C14.8599 4.10339 14.8193 3.98931 14.8193 3.86096V2.47059C14.8193 2.32799 14.8599 2.2139 14.941 2.12834C15.0357 2.04278 15.1439 2 15.2656 2H15.367Z" fill="currentColor"/>
  
    </svg>
  );
};



================================================
FILE: app/components/Icons/ShortcutIcon.tsx
================================================
export type ShortcutIconProps = {
  children: React.ReactNode;
  className?: string;
};

export function ShortcutIcon({ className, children }: ShortcutIconProps) {
  return (
    <span
      className={`flex items-center justify-center rounded ${className ?? ""}`}
    >
      {children}
    </span>
  );
}


================================================
FILE: app/components/Icons/SquareBracketsIcon.tsx
================================================
export function SquareBracketsIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="30"
      height="14"
      viewBox="0 0 30 14"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <rect width="14" height="14" rx="1.53846" fill="currentColor" />
      <path d="M6 11V3H9V4.5H7.5V9.5H9V11H6Z" fill="#0F172A" />
      <rect x="16" width="14" height="14" rx="1.53846" fill="currentColor" />
      <path
        d="M25 3V11L21.9997 11V9.5H23.5V4.5H21.9997V3.00002L25 3Z"
        fill="#0F172A"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/StringIcon.tsx
================================================
export function StringIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      viewBox="-2 -5 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M4.536 6.845a3.908 3.908 0 0 1 2.598.566l-.025-.032a4.114 4.114 0 0 1 1.766 2.478 4.228 4.228 0 0 1-.391 3.047 4.026 4.026 0 0 1-2.33 1.92 3.898 3.898 0 0 1-2.973-.273h-.03a1.296 1.296 0 0 0-.082-.045c-.033-.018-.066-.035-.096-.056a4.236 4.236 0 0 1-.99-.88A7.746 7.746 0 0 1 .095 9.743a8.717 8.717 0 0 1 .191-3.498c.3-1.14.827-2.203 1.55-3.12A8.282 8.282 0 0 1 4.477.918 8.047 8.047 0 0 1 7.763 0c.287 0 .562.117.765.326.203.209.317.492.317.787 0 .296-.114.579-.317.788a1.066 1.066 0 0 1-.765.326c-.96.04-1.895.332-2.716.847A5.758 5.758 0 0 0 3.07 5.17a6.53 6.53 0 0 0-.905 2.906 3.962 3.962 0 0 1 2.37-1.232ZM15.53 6.83c.901-.12 1.815.079 2.591.565h-.006a4.105 4.105 0 0 1 1.761 2.473 4.22 4.22 0 0 1-.39 3.04 4.016 4.016 0 0 1-2.324 1.917 3.886 3.886 0 0 1-2.966-.273h-.036c-.03-.021-.063-.038-.097-.056a1.317 1.317 0 0 1-.08-.045 4.226 4.226 0 0 1-.987-.879 7.782 7.782 0 0 1-1.902-3.848 8.715 8.715 0 0 1 .194-3.49 8.564 8.564 0 0 1 1.546-3.111A8.276 8.276 0 0 1 15.469.92 8.037 8.037 0 0 1 18.742 0c.286 0 .56.117.763.325.202.209.316.491.316.786 0 .295-.114.577-.316.786a1.063 1.063 0 0 1-.763.325 5.526 5.526 0 0 0-2.707.846 5.738 5.738 0 0 0-1.968 2.092 6.519 6.519 0 0 0-.902 2.9 3.952 3.952 0 0 1 2.364-1.23Z"
        fill="currentColor"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/SunIcon.tsx
================================================
import { motion } from "framer-motion";
import { transition } from "../../utilities/animationConstants";

export const SunIcon = () => {
  const whileTap = { scale: 0.95, rotate: 15 };

  const raysVariants = {
    initial: { rotate: 45 },
    animate: { rotate: 0, transition },
  };

  const coreVariants = {
    initial: { scale: 1.5 },
    animate: { scale: 1, transition },
  };

  return (
    <motion.svg
      key="sun"
      width="1em"
      height="1em"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      whileTap={whileTap}
      // Centers the rotation anchor point vertically & horizontally
      style={{ originX: "50%", originY: "50%" }}
    >
      <motion.circle
        cx="11.9998"
        cy="11.9998"
        r="5.75375"
        fill="currentColor"
        initial="initial"
        animate="animate"
        variants={coreVariants}
      />
      <motion.g initial="initial" animate="animate" variants={raysVariants}>
        <circle
          cx="3.08982"
          cy="6.85502"
          r="1.71143"
          transform="rotate(-60 3.08982 6.85502)"
          fill="currentColor"
        />
        <circle
          cx="3.0903"
          cy="17.1436"
          r="1.71143"
          transform="rotate(-120 3.0903 17.1436)"
          fill="currentColor"
        />
        <circle cx="12" cy="22.2881" r="1.71143" fill="currentColor" />
        <circle
          cx="20.9101"
          cy="17.1436"
          r="1.71143"
          transform="rotate(-60 20.9101 17.1436)"
          fill="currentColor"
        />
        <circle
          cx="20.9101"
          cy="6.8555"
          r="1.71143"
          transform="rotate(-120 20.9101 6.8555)"
          fill="currentColor"
        />
        <circle cx="12" cy="1.71143" r="1.71143" fill="currentColor" />
      </motion.g>
    </motion.svg>
  );
};


================================================
FILE: app/components/Icons/TreeIcon.tsx
================================================
export function TreeIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="28"
      height="30"
      viewBox="0 0 28 30"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M14.1805 30C14.5093 30 14.8245 29.8694 15.0571 29.6368C15.2895 29.4044 15.4201 29.089 15.4201 28.7602V22.5619H21.6184C23.2624 22.5619 24.839 21.9091 26.0014 20.7467C27.1638 19.5843 27.8167 18.0077 27.8167 16.3636C27.8234 14.3646 26.854 12.4882 25.2197 11.3368C25.2971 10.9512 25.3364 10.5588 25.3375 10.1653C25.3379 8.64671 24.7808 7.18076 23.7722 6.04565C22.7633 4.91048 21.3726 4.18522 19.8643 4.00752C19.2285 2.5124 18.0292 1.3282 16.5263 0.711248C15.0232 0.0942975 13.3379 0.0942975 11.8348 0.711248C10.332 1.3282 9.13259 2.51246 8.49682 4.00752C6.98853 4.18522 5.59785 4.91048 4.58897 6.04565C3.58025 7.18082 3.02318 8.64671 3.02362 10.1653C3.0247 10.5588 3.06405 10.9511 3.14144 11.3368C1.50714 12.4882 0.537772 14.3646 0.544468 16.3636C0.544468 18.0077 1.19734 19.5843 2.35974 20.7467C3.52214 21.9091 5.09872 22.5619 6.74276 22.5619H12.941V28.7602C12.941 29.089 13.0716 29.4044 13.304 29.6368C13.5366 29.8694 13.8518 30 14.1806 30H14.1805ZM6.74257 20.0827C5.75616 20.0827 4.81014 19.6908 4.11272 18.9934C3.41531 18.296 3.02337 17.35 3.02337 16.3636C3.02273 15.6644 3.22118 14.9796 3.59561 14.389C3.96981 13.7986 4.50443 13.3267 5.13716 13.029C5.41473 12.8941 5.63221 12.6606 5.7468 12.3742C5.86138 12.0875 5.86505 11.7685 5.75696 11.4794C5.31725 10.3533 5.4569 9.0835 6.13052 8.07999C6.80414 7.07625 7.92631 6.466 9.13497 6.4463C9.18145 6.4463 9.30857 6.46489 9.35202 6.46489C9.63457 6.47851 9.91302 6.39311 10.1391 6.22341C10.3655 6.05371 10.5255 5.8103 10.5916 5.53507C10.8614 4.46133 11.5979 3.56463 12.599 3.0914C13.6002 2.6184 14.7606 2.6184 15.7617 3.0914C16.7628 3.56461 17.4993 4.46133 17.7691 5.53507C17.8363 5.80962 17.9965 6.05239 18.2227 6.22186C18.4486 6.39135 18.7266 6.47739 19.0087 6.46485C19.083 6.46485 19.1544 6.46485 19.1388 6.44928L19.139 6.4495C20.3615 6.43934 21.5099 7.03579 22.2045 8.04207C22.8991 9.04841 23.0497 10.3333 22.607 11.473C22.4987 11.7621 22.5024 12.0811 22.6169 12.3678C22.7317 12.6545 22.949 12.8879 23.2268 13.0226C24.2385 13.5174 24.9716 14.444 25.2202 15.5424C25.4691 16.6408 25.2066 17.7928 24.5066 18.675C23.8066 19.5572 22.7445 20.0748 21.6182 20.0826H15.42V16.9151L19.8735 13.6424C20.1495 13.4519 20.3365 13.1579 20.3919 12.8272C20.4472 12.4965 20.3664 12.1575 20.1677 11.8875C19.969 11.6175 19.6692 11.4394 19.3371 11.3942C19.0049 11.3488 18.6685 11.4398 18.4045 11.6467L15.4199 13.8379V8.92563C15.4199 8.48267 15.1836 8.07347 14.8002 7.8521C14.4167 7.63074 13.9441 7.63074 13.5606 7.8521C13.1771 8.07347 12.9408 8.48272 12.9408 8.92563V15.1239L9.96256 12.9018C9.60716 12.6372 9.13741 12.5823 8.73054 12.7578C8.32368 12.9334 8.04136 13.3125 7.9899 13.7527C7.93844 14.1927 8.12565 14.627 8.48105 14.8916L12.9408 18.2045V20.0827L6.74257 20.0827Z"
        fill="currentColor"
      />
    </svg>
  );
}


================================================
FILE: app/components/Icons/TwitterIcon.tsx
================================================
export function TwitterIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg
      className={props.className}
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <circle cx="12" cy="12" r="12" fill="#F8FAFC" />
      <path
        d="M5.43319 6H5.44396C5.50679 6.07822 5.56783 6.15853 5.63271 6.23467C6.27615 6.99369 6.98755 7.67709 7.80461 8.24238C8.73734 8.88746 9.75135 9.33515 10.8628 9.5487C11.3459 9.64122 11.8363 9.68896 12.3279 9.69132C12.3435 9.69076 12.359 9.68937 12.3744 9.68715C12.3664 9.65326 12.3582 9.62536 12.3538 9.59694C12.2968 9.24285 12.298 8.8816 12.3572 8.52789C12.5788 7.19811 13.6625 6.20938 14.9433 6.03181C15.0276 6.02008 15.1125 6.01069 15.1971 6H15.5621C15.5811 6.00425 15.6004 6.00747 15.6198 6.00965C15.9463 6.0339 16.2671 6.10973 16.5707 6.23441C16.9759 6.40128 17.347 6.64347 17.665 6.94858C17.6748 6.9577 17.6863 6.96472 17.6988 6.9692C17.7113 6.97368 17.7246 6.97554 17.7378 6.97465C18.2937 6.87178 18.8309 6.68327 19.3309 6.41562C19.4296 6.36347 19.5276 6.30924 19.6387 6.24901C19.3596 6.95666 18.9111 7.50057 18.2866 7.89872C18.8855 7.84345 19.4486 7.66119 20 7.42652C19.9379 7.51934 19.8738 7.60773 19.8071 7.6943C19.4338 8.17928 19.0121 8.61524 18.5141 8.96803C18.4917 8.98129 18.4736 9.00084 18.4618 9.02433C18.4501 9.04783 18.4453 9.07427 18.4479 9.10048C18.4686 9.8569 18.3891 10.6127 18.2115 11.3476C17.928 12.5242 17.41 13.6293 16.6897 14.5943C15.6526 15.9935 14.2124 17.0293 12.5695 17.5579C12.1008 17.7133 11.6191 17.8245 11.1303 17.8901C10.8077 17.9305 10.4838 17.9657 10.1596 17.9845C9.618 18.016 9.07662 17.996 8.53576 17.9558C8.04402 17.9215 7.55544 17.8504 7.07398 17.743C6.48868 17.6143 5.9222 17.4092 5.38857 17.1329C5.26034 17.0656 5.13699 16.9916 5.01132 16.9207L5.01517 16.9071H5.07672C5.59886 16.9102 6.12023 16.9071 6.63826 16.8229C7.15926 16.7393 7.66519 16.5776 8.13954 16.3431C8.63963 16.0944 9.09073 15.7695 9.53619 15.4334C9.54091 15.4282 9.54481 15.4223 9.54773 15.4159C9.1274 15.4081 8.75888 15.2882 8.4837 15.1716C7.99209 14.9607 7.53859 14.6678 7.14194 14.3049C6.77034 13.9696 6.44618 13.5941 6.21717 13.1417C6.15818 13.0254 6.11177 12.9026 6.05535 12.7736C6.52466 12.8779 6.96935 12.8317 7.40942 12.7071C5.75248 12.3707 4.93798 10.8409 5.00748 9.69654C5.43653 9.89705 5.89019 9.99691 6.35411 10.0566C6.22307 9.94633 6.08663 9.84568 5.96661 9.72757C5.13109 8.90571 4.83078 7.91854 5.09083 6.76267C5.15514 6.48825 5.27143 6.2292 5.43319 6Z"
        fill="#4338CA"
      />
    </svg>
  );
}


================================================
FILE: app/components/IndentPreference.tsx
================================================
import React from "react";
import { Body } from "./Primitives/Body";
import { usePreferences } from "~/components/PreferencesProvider";

const MIN_INDENT = 1;
const MAX_INDENT = 8;

export function IndentPreference() {
  const [preferences, setPreferences] = usePreferences();

  const updatePreferences = (e: React.ChangeEvent<HTMLInputElement>) => {
    let newIdent = Number(e.target.value);
    if (newIdent < MIN_INDENT) newIdent = MIN_INDENT;
    if (newIdent > MAX_INDENT) newIdent = MAX_INDENT;
    e.target.value = newIdent.toString();
    setPreferences({ ...preferences, indent: newIdent });
  };

  return (
    <div className="flex items-center -mt-0.5">
      <label
        className="pr-2 text-slate-800 transition dark:text-white"
        htmlFor="indent"
      >
        <Body>Indent</Body>
      </label>
      <input
        type="number"
        className="py-0 pr-0 pl-1 w-9 rounded-sm text-sm h-[23px] bg-slate-300 transition hover:bg-slate-400 hover:bg-opacity-50 dark:bg-slate-800 dark:text-slate-400 hover:cursor-pointer hover:dark:bg-slate-700 hover:dark:bg-opacity-70"
        defaultValue={preferences?.indent}
        min={MIN_INDENT}
        max={MAX_INDENT}
        onChange={updatePreferences}
      />
    </div>
  );
}


================================================
FILE: app/components/InfoHeader.tsx
================================================
import { inferType } from "@jsonhero/json-infer-types";
import { JSONHeroPath } from "@jsonhero/path";
import { useCallback, useMemo, useState } from "react";
import { useJson } from "~/hooks/useJson";
import {
  useJsonColumnViewAPI,
  useJsonColumnViewState,
} from "~/hooks/useJsonColumnView";
import { concatenated, getHierarchicalTypes } from "~/utilities/dataType";
import { formatRawValue } from "~/utilities/formatter";
import { isNullable } from "~/utilities/nullable";
import { CopyTextButton } from "./CopyTextButton";
import { Body } from "./Primitives/Body";
import { LargeMono } from "./Primitives/LargeMono";
import { Title } from "./Primitives/Title";
import { ValueIcon, ValueIconSize } from "./ValueIcon";

export type InfoHeaderProps = {
  relatedPaths: string[];
};

export function InfoHeader({ relatedPaths }: InfoHeaderProps) {
  const { selectedNodeId, highlightedNodeId, selectedNodes } =
    useJsonColumnViewState();
  const { goToNodeId } = useJsonColumnViewAPI();

  if (!selectedNodeId || !highlightedNodeId || selectedNodes.length === 0) {
    return <EmptyState />;
  }

  const selectedNode = selectedNodes[selectedNodes.length - 1];

  const [json] = useJson();

  const selectedHeroPath = new JSONHeroPath(selectedNodeId);
  const selectedJson = selectedHeroPath.first(json);
  const selectedInfo = inferType(selectedJson);
  const formattedSelectedInfo = formatRawValue(selectedInfo);
  const selectedName = selectedNode.longTitle ?? selectedNode.title;

  const isSelectedLeafNode =
    selectedInfo.name !== "object" && selectedInfo.name !== "array";

  const canBeNull = useMemo(() => {
    return isNullable(relatedPaths, json);
  }, [relatedPaths, json]);

  const [hovering, setHovering] = useState(false);
  console.warn(selectedInfo);

  const newPath = formattedSelectedInfo.replace(/^#/, "$").replace(/\//g, ".");

  const handleClick = useCallback(() => {
    goToNodeId(newPath, "pathBar");
  }, [newPath, goToNodeId]);

  return (
    <div className="mb-4 pb-4">
      <div className="flex items-center">
        <Title className="flex-1 mr-2 overflow-hidden overflow-ellipsis break-words text-slate-700 transition dark:text-slate-200">
          { selectedName ?? "nothing" }
        </Title>
        <div>
          <ValueIcon
            monochrome
            type={selectedInfo}
            size={ValueIconSize.Medium}
          />
        </div>
      </div>
      <div
        className="relative w-full h-full"
        onMouseEnter={() => setHovering(true)}
        onMouseLeave={() => setHovering(false)}
      >
        {isSelectedLeafNode && (
          <LargeMono
            className={`z-10 py-1 mb-1 text-slate-800 overflow-ellipsis break-words transition rounded-sm dark:text-slate-300 ${
              hovering ? "bg-slate-100 dark:bg-slate-700" : "bg-transparent"
            }`}
          >
            {selectedNode.name === "$ref" && checkPathExists(json, newPath) ? (
              <button onClick={handleClick}>
                {formatRawValue(selectedInfo)}
              </button>
            ) : (
              formatRawValue(selectedInfo)
            )}
          </LargeMono>
        )}
        <div
          className={`absolute top-1 right-0 flex justify-end h-full w-fit transition ${
            hovering ? "opacity-100" : "opacity-0"
          }`}
        >
          <CopyTextButton
            className="bg-slate-200 hover:bg-slate-300 h-fit mr-1 px-2 py-0.5 rounded-sm transition hover:cursor-pointer dark:text-white dark:bg-slate-600 dark:hover:bg-slate-500"
            value={formatRawValue(selectedInfo)}
          ></CopyTextButton>
        </div>
      </div>
      <div className="flex text-gray-400">
        <Body className="flex-1">
          {concatenated(getHierarchicalTypes(selectedInfo))}
        </Body>
        {canBeNull && <Body>Can be null</Body>}
      </div>
    </div>
  );
}

function checkPathExists(json: unknown, newPath: string) {
  const heroPath = new JSONHeroPath(newPath);
  const node = heroPath.first(json);
  return Boolean(node);
}

function EmptyState() {
  return (
    <div className="mb-4 pb-4 border-b border-slate-300">
      <div className="flex items-center">
        <Title className="flex-1 mr-2 text-slate-800 transition dark:text-slate-300">
          Nothing selected
        </Title>
      </div>
      <div>
        <div>
          <Title className="text-slate-800 mb-1 overflow-ellipsis break-words dark:text-slate-300">
            null
          </Title>
        </div>
      </div>
    </div>
  );
}


================================================
FILE: app/components/InfoPanel.tsx
================================================
import { PreviewValue } from "./Preview/PreviewValue";
import { RelatedValues } from "./RelatedValues";
import { PropertiesValue } from "./Properties/PropertiesValue";
import { InfoHeader } from "./InfoHeader";
import { ContainerInfo } from "./ContainerInfo";
import { useSelectedInfo } from "~/hooks/useSelectedInfo";
import { useRelatedPaths } from "~/hooks/useRelatedPaths";
import { useJsonDoc } from "~/hooks/useJsonDoc";

export function InfoPanel() {
  const { minimal } = useJsonDoc();
  const selectedInfo = useSelectedInfo();
  const relatedPaths = useRelatedPaths();

  if (!selectedInfo) {
    return <></>;
  }

  return (
    <>
      <div
        className={`${
          minimal ? "h-inspectorHeightMinimal" : "h-inspectorHeight"
        } p-4 bg-white border-l-[1px] border-slate-300 overflow-y-auto no-scrollbar transition dark:bg-slate-800 dark:border-slate-600`}
      >
        <InfoHeader relatedPaths={relatedPaths} />

        <div className="mb-4">
          <PreviewValue />
        </div>
        <PropertiesValue />

        <ContainerInfo />

        <RelatedValues relatedPaths={relatedPaths} />
      </div>
    </>
  );
}


================================================
FILE: app/components/JsonColumnView.tsx
================================================
import {
  useJsonColumnViewAPI,
  useJsonColumnViewState,
} from "../hooks/useJsonColumnView";
import { useHotkeys } from "react-hotkeys-hook";
import { Columns } from "./Columns";
import { CopySelectedNodeShortcut } from "./CopySelectedNode";

export function JsonColumnView() {
  const { getColumnViewProps, columns } = useJsonColumnViewState();

  return (
    <>
      <KeyboardShortcuts />
      <div {...getColumnViewProps()}>
        <Columns columns={columns} />
      </div>
    </>
  );
}

function KeyboardShortcuts() {
  const api = useJsonColumnViewAPI();

  useHotkeys(
    "down",
    (e) => {
      e.preventDefault();
      api.goToNextSibling();
    },
    { enabled: true },
    [api]
  );

  useHotkeys(
    "up",
    (e) => {
      e.preventDefault();
      api.goToPreviousSibling();
    },
    [api]
  );

  useHotkeys(
    "right",
    (e) => {
      e.preventDefault();
      api.goToChildren();
    },
    [api]
  );

  useHotkeys(
    "left,alt+left",
    (e) => {
      e.preventDefault();
      api.goToParent({ source: e });
    },
    [api]
  );

  useHotkeys(
    "esc",
    (e) => {
      e.preventDefault();
      api.resetSelection();
    },
    [api]
  );

  return <>
    <CopySelectedNodeShortcut />
  </>;
}


================================================
FILE: app/components/JsonEditor.tsx
================================================
import { CodeEditor } from "./CodeEditor";
import { useJson } from "~/hooks/useJson";
import { useCallback, useMemo, useRef } from "react";
import {
  useJsonColumnViewAPI,
  useJsonColumnViewState,
} from "~/hooks/useJsonColumnView";
import { ViewUpdate } from "@uiw/react-codemirror";
import jsonMap from "json-source-map";
import { JSONHeroPath } from "@jsonhero/path";
import {usePreferences} from '~/components/PreferencesProvider'

export function JsonEditor() {
  const [json] = useJson();
  const { selectedNodeId } = useJsonColumnViewState();
  const { goToNodeId } = useJsonColumnViewAPI();
  const [preferences] = usePreferences();

  const jsonMapped = useMemo(() => {
    return jsonMap.stringify(json, null, preferences?.indent || 2);
  }, [json, preferences]);

  const selection = useMemo<{ start: number; end: number } | undefined>(() => {
    if (!selectedNodeId) {
      return;
    }

    const path = new JSONHeroPath(selectedNodeId);
    const pointer = path.jsonPointer();

    const location = jsonMapped.pointers[pointer];

    if (location) {
      if (location.key) {
        return { start: location.key.pos, end: location.valueEnd.pos };
      }

      return { start: location.value.pos, end: location.valueEnd.pos };
    }
  }, [selectedNodeId, jsonMapped]);

  const currentSelectedLine = useRef<number | undefined>(undefined);

  const onUpdate = useCallback(
    (update: ViewUpdate) => {
      if (!update.selectionSet) {
        return;
      }

      const range = update.state.selection.ranges[0];
      const line = update.state.doc.lineAt(range.anchor);

      if (
        currentSelectedLine.current &&
        currentSelectedLine.current === line.number
      ) {
        return;
      }

      currentSelectedLine.current = line.number;

      // Find the key if the selected line using jsonMapped.pointers
      const pointerEntry = Object.entries(jsonMapped.pointers).find(
        ([pointer, info]) => {
          return info.value.line === line.number - 1;
        }
      );

      if (!pointerEntry) {
        return;
      }

      const [pointer] = pointerEntry;

      const path = JSONHeroPath.fromPointer(pointer);

      goToNodeId(path.toString(), "editor");
    },
    [goToNodeId]
  );

  return (
    <CodeEditor
      language="json"
      content={jsonMapped.json}
      readOnly={true}
      onUpdate={onUpdate}
      selection={selection}
    />
  );
}


================================================
FILE: app/components/JsonPreview.tsx
================================================
import { RangeSetBuilder } from "@codemirror/rangeset";
import { JSONHeroPath } from "@jsonhero/path";
import {
  useCodeMirror,
  EditorView,
  Decoration,
  Facet,
  ViewPlugin,
  Compartment,
  TransactionSpec,
} from "@uiw/react-codemirror";
import jsonMap from "json-source-map";
import { useRef, useEffect, useMemo, useState } from "react";
import { getPreviewSetup } from "~/utilities/codeMirrorSetup";
import { lightTheme, darkTheme } from "~/utilities/codeMirrorTheme";
import { CopyTextButton } from "./CopyTextButton";
import { useTheme } from "./ThemeProvider";
import {usePreferences} from '~/components/PreferencesProvider';
import { useHotkeys } from "react-hotkeys-hook";

export type JsonPreviewProps = {
  json: unknown;
  highlightPath?: string;
};

export function JsonPreview({ json, highlightPath }: JsonPreviewProps) {
  const editor = useRef(null);
  const [preferences] = usePreferences();

  const jsonMapped = useMemo(() => {
    return jsonMap.stringify(json, null, preferences?.indent || 2);
  }, [json, preferences]);

  const lines: LineRange | undefined = useMemo(() => {
    if (!highlightPath) {
      return;
    }

    let path = new JSONHeroPath(highlightPath);
    let pointer = path.jsonPointer();

    let selectionInfo = jsonMapped.pointers[pointer];

    return {
      from: selectionInfo.value.line + 1,
      to: selectionInfo.valueEnd.line + 1,
    };
  }, [jsonMapped, highlightPath]);

  const extensions = getPreviewSetup();

  const highlighting = new Compartment();

  if (lines) {
    extensions.push(highlighting.of(highlightLineRange(lines)));
  }

  const [theme] = useTheme();

  const { setContainer, view, state } = useCodeMirror({
    container: editor.current,
    extensions,
    value: jsonMapped.json,
    editable: false,
    contentEditable: false,
    autoFocus: false,
    basicSetup: false,
    theme: theme === "light" ? lightTheme() : darkTheme(),
  });

  useEffect(() => {
    if (editor.current) {
      setContainer(editor.current);
    }
  }, [editor.current]);

  useEffect(() => {
    if (!view) {
      return;
    }

    let transactionSpec: TransactionSpec = {
      changes: { from: 0, to: view.state.doc.length, insert: jsonMapped.json },
    };

    let range = lines;
    if (range != null) {
      transactionSpec.effects = highlighting.reconfigure(
        highlightLineRange(range)
      );
    }

    view.dispatch(transactionSpec);
  }, [view, highlighting, jsonMapped, highlightPath]);

  useHotkeys(
    "ctrl+a,meta+a,command+a",
    (e) => {
      e.preventDefault();
      view?.dispatch({ selection: { anchor: 0, head: state?.doc.length } });
    },
    [view, state]
  );

  const [hovering, setHovering] = useState(false);

  return (
    <div
      className="relative w-full h-full"
      onMouseEnter={() => setHovering(true)}
      onMouseLeave={() => setHovering(false)}
    >
      <div ref={editor} />
      <div
        className={`absolute top-1 right-0 flex justify-end w-full transition ${
          hovering ? "opacity-100" : "opacity-0"
        }`}
      >
        <CopyTextButton
          value={jsonMapped.json}
          className="bg-slate-200 hover:bg-slate-300 h-fit mr-1 px-2 py-0.5 rounded-sm transition hover:cursor-pointer dark:text-white dark:bg-slate-700 dark:hover:bg-slate-600"
        ></CopyTextButton>
      </div>
    </div>
  );
}

interface LineRange {
  from: number;
  to: number;
}

const baseTheme = EditorView.baseTheme({
  "&light .cm-highlighted": { backgroundColor: "#ffee0055" },
  "&dark .cm-highlighted": { backgroundColor: "#ffee0055" },
});

const highlightedRange = Facet.define<LineRange, LineRange>({
  combine: (values) => (values.length ? values[0] : { from: -1, to: -1 }),
});

function highlightLineRange(range: LineRange | null) {
  return [
    baseTheme,
    range == null ? [] : highlightedRange.of(range),
    highlightLineRangePlugin,
  ];
}
const lineHighlightDecoration = Decoration.line({
  attributes: { class: "cm-highlighted" },
});

function highlightLines(view: EditorView) {
  let highlightRange = view.state.facet(highlightedRange);
  let builder = new RangeSetBuilder();
  for (let { from, to } of view.visibleRanges) {
    for (let pos = from; pos <= to; ) {
      let line = view.state.doc.lineAt(pos);
      if (
        line.number >= highlightRange.from &&
        line.number <= highlightRange.to
      ) {
        builder.add(line.from, line.from, lineHighlightDecoration);
      }
      pos = line.to + 1;
    }
  }
  return builder.finish();
}

const highlightLineRangePlugin = ViewPlugin.fromClass(
  class {
    decorations: any;
    constructor(view: any) {
      this.decorations = highlightLines(view);
    }

    update(update: { docChanged: any; viewportChanged: any; view: any }) {
      if (update.docChanged || update.viewportChanged)
        this.decorations = highlightLines(update.view);
    }
  },
  {
    decorations: (v) => v.decorations,
  }
);


================================================
FILE: app/components/JsonSchemaViewer.tsx
================================================
import { JSONHeroPath } from "@jsonhero/path";
import { useMemo, useState } from "react";
import { useJsonSchema } from "~/hooks/useJsonSchema";
import { CodeViewer } from "./CodeViewer";
import { CopyTextButton } from "./CopyTextButton";
import {usePreferences} from '~/components/PreferencesProvider'

export function JsonSchemaViewer({ path }: { path: string }) {
  const schema = useJsonSchema();
  const schemaPath = schemaPathFromPath(path);
  const schemaJson = schemaPath.first(schema);
  const [hovering, setHovering] = useState(false);
  const [preferences] = usePreferences();

  const code = useMemo(() => {
    return JSON.stringify(schemaJson, null, preferences?.indent || 2);
  }, [schemaJson, preferences]);

  return (
    <div
      className="relative w-full h-full"
      onMouseEnter={() => setHovering(true)}
      onMouseLeave={() => setHovering(false)}
    >
      <CodeViewer code={code} lang="json" />
      <div
        className={`absolute top-1 right-0 flex justify-end w-full transition ${
          hovering ? "opacity-100" : "opacity-0"
        }`}
      >
        <CopyTextButton
          value={code}
          className="bg-slate-200 hover:bg-slate-300 h-fit mr-1 px-2 py-0.5 rounded-sm transition hover:cursor-pointer dark:text-white dark:bg-slate-700 dark:hover:bg-slate-600"
        ></CopyTextButton>
      </div>
    </div>
  );
}

function schemaPathFromPath(path: JSONHeroPath | string): JSONHeroPath {
  const heroPath = typeof path === "string" ? new JSONHeroPath(path) : path;

  if (heroPath.isRoot) {
    return heroPath;
  }

  return heroPath.components.slice(1).reduce((acc, component) => {
    if (component.isArray) {
      return acc.child("items");
    } else {
      return acc.child("properties").child(component.toString());
    }
  }, new JSONHeroPath("$"));
}


================================================
FILE: app/components/JsonTreeView.tsx
================================================
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline";
import { useEffect, useRef } from "react";
import {
  useJsonColumnViewAPI,
  useJsonColumnViewState,
} from "~/hooks/useJsonColumnView";
import { useJsonDoc } from "~/hooks/useJsonDoc";
import { JsonTreeViewNode, useJsonTreeViewContext } from "~/hooks/useJsonTree";
import { VirtualNode } from "~/hooks/useVirtualTree";
import { CopySelectedNodeShortcut } from "./CopySelectedNode";
import { Body } from "./Primitives/Body";
import { Mono } from "./Primitives/Mono";

export function JsonTreeView() {
  const { selectedNodeId, selectedNodeSource } = useJsonColumnViewState();
  const { goToNodeId } = useJsonColumnViewAPI();

  const { tree, parentRef } = useJsonTreeViewContext();

  // Scroll to the selected node when this component is first rendered.
  const scrolledToNodeRef = useRef(false);

  useEffect(() => {
    if (!scrolledToNodeRef.current && selectedNodeId) {
      tree.scrollToNode(selectedNodeId);
      scrolledToNodeRef.current = true;
    }
  }, [selectedNodeId, scrolledToNodeRef]);

  // Yup, this is hacky.
  // This is to prevent the selection not changing the first time you try to move to a new node in the tree
  const focusCount = useRef<number>(0);

  // This focuses and scrolls to the selected node when the selectedNodeId
  // is set from a source other than this tree (e.g. the search bar, path bar, related values).
  useEffect(() => {
    if (
      tree.focusedNodeId &&
      selectedNodeId &&
      tree.focusedNodeId !== selectedNodeId
    ) {
      if (selectedNodeId === "$") {
        return;
      }

      if (selectedNodeSource !== "tree" && focusCount.current > 0) {
        focusCount.current = focusCount.current + 1;
        tree.focusNode(selectedNodeId);
        tree.scrollToNode(selectedNodeId);
      }
    }
  }, [tree.focusedNodeId, goToNodeId, selectedNodeId, selectedNodeSource]);

  // This is what syncs the tree view's focused node to the column view selected node
  const previousFocusedNodeId = useRef<string | null>(null);

  useEffect(() => {
    let updated = false;

    if (!previousFocusedNodeId.current) {
      previousFocusedNodeId.current = tree.focusedNodeId;
      updated = true;
    }

    if (
      tree.focusedNodeId &&
      (updated || previousFocusedNodeId.current !== tree.focusedNodeId)
    ) {
      previousFocusedNodeId.current = tree.focusedNodeId;
      goToNodeId(tree.focusedNodeId, "tree");
    }
  }, [previousFocusedNodeId, tree.focusedNodeId, tree.focusNode, goToNodeId]);

  const treeRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (treeRef.current) {
      treeRef.current.focus({ preventScroll: true });
    }
  }, [treeRef.current]);

  const { minimal } = useJsonDoc();

  return (
    <>
      <CopySelectedNodeShortcut />
      <div
        className="text-white w-full"
        ref={parentRef}
        style={{
          height: `calc(100vh - ${minimal ? "66px" : "106px"})`,
          overflowY: "auto",
          overflowX: "hidden",
        }}
      >
        <div
          className="relative w-full outline-none"
          style={{ height: `${tree.totalSize}px` }}
          {...tree.getTreeProps()}
          ref={treeRef}
        >
          {tree.nodes.map((virtualNode) => (
            <TreeViewNode
              virtualNode={virtualNode}
              key={virtualNode.node.id}
              onToggle={(node, e) => tree.toggleNode(node.id, e)}
              selectedNodeId={selectedNodeId}
            />
          ))}
        </div>
      </div>
    </>
  );
}

function TreeViewNode({
  virtualNode,
  onToggle,
  selectedNodeId,
}: {
  virtualNode: VirtualNode<JsonTreeViewNode>;
  selectedNodeId?: string;
  onToggle?: (node: JsonTreeViewNode, e: MouseEvent) => void;
}) {
  const { node, virtualItem, depth } = virtualNode;

  const indentClassName = computeTreeNodePaddingClass(depth);

  const isSelected = selectedNodeId === node.id;

  return (
    <div
      style={{
        position: "absolute",
        top: 0,
        left: 0,
        width: "100%",
        height: `${virtualNode.size}px`,
        transform: `translateY(${virtualNode.start}px)`,
      }}
      key={virtualNode.node.id}
      {...virtualNode.getItemProps()}
    >
      <div
        className={`h-full flex pl-5 rounded-sm select-none ${
          isSelected
            ? "bg-indigo-700"
            : virtualItem.index % 2
            ? "dark:bg-slate-900"
            : "bg-slate-100 bg-opacity-90 dark:bg-slate-800 dark:bg-opacity-30"
        }`}
      >
        <div className={`pl-2 w-2/6 items-center flex`}>
          {node.children && node.children.length > 0 && (
            <span
              onClick={(e) => {
                if (onToggle) {
                  e.preventDefault();
                  onToggle(node, e.nativeEvent);
                }
              }}
            >
              {virtualNode.isCollapsed ? (
                <ChevronRightIcon
                  className={`w-4 h-4 mr-1 -ml-5  ${
                    isSelected
                      ? "text-slate-100"
                      : "text-slate-600 dark:text-slate-100"
                  }`}
                />
              ) : (
                <ChevronDownIcon
                  className={`w-4 h-4 mr-1 -ml-5 ${
                    isSelected
                      ? "text-slate-100"
                      : "text-slate-600 dark:text-slate-100"
                  }`}
                />
              )}
            </span>
          )}

          <Body
            className={`${indentClassName} leading-8 truncate whitespace-nowrap pl-2 pr-2 ${
              isSelected
                ? "text-slate-100"
                : "text-slate-700 dark:text-slate-200"
            }`}
          >
            {node.longTitle ?? node.name}
          </Body>
        </div>

        <div className="flex w-4/6 items-center">
          <span className="mr-2">
            {node.icon && (
              <node.icon
                className={`h-5 w-5 ${
                  isSelected
                    ? "text-slate-100"
                    : "text-slate-400 dark:text-slate-500"
                }`}
              />
            )}
          </span>
          {node.subtitle && (
            <Mono
              className={`truncate pr-1 transition ${
                isSelected
                  ? "text-slate-100"
                  : "text-slate-500 dark:text-slate-200"
              }`}
            >
              {node.subtitle}
            </Mono>
          )}
        </div>
      </div>
    </div>
  );
}

function computeTreeNodePaddingClass(depth: number) {
  switch (depth) {
    case 0:
      return "ml-[4px] border-l border-slate-400/70";
    case 1:
      return "ml-[calc(12px_+_4px)] border-l border-pink-400/70";
    case 2:
      return "ml-[calc(12px_*_2_+_4px)] border-l border-blue-400/70";
    case 3:
      return "ml-[calc(12px_*_3_+_4px)] border-l border-orange-400/70";
    case 4:
      return "ml-[calc(12px_*_4_+_4px)] border-l border-emerald-400/70";
    case 5:
      return "ml-[calc(12px_*_5_+_4px)] border-l border-pink-400/70";
    case 6:
      return "ml-[calc(12px_*_6_+_4px)] border-l border-blue-400/70";
    case 7:
      return "ml-[calc(12px_*_7_+_4px)] border-l border-orange-400/70";
    case 8:
      return "ml-[calc(12px_*_8_+_4px)] border-l border-emerald-400/70";
    case 9:
      return "ml-[calc(12px_*_9_+_4px)] border-l border-pink-400/70";
    case 10:
      return "ml-[calc(12px_*_10_+_4px)] border-l border-orange-400/70";
    default:
      return "ml-[calc(12px_*_11_+_4px)] border-l border-slate-400/70";
  }
}


================================================
FILE: app/components/JsonView.tsx
================================================
import React from "react";
import { PathBar, PathHistoryControls } from "./PathBar";
import { SearchBar } from "./SearchBar";

export function JsonView({ children }: { children: React.ReactNode }) {
  return (
    <div className="path-bar-and-column-wrapper flex flex-col flex-grow overflow-x-hidden border-l-[1px] border-slate-300 transition dark:border-slate-600">
      <div className="flex justify-between p-1 bg-slate-200 border-slate-300 border-b-[1px] transition dark:bg-slate-900 dark:border-slate-600">
        <div className="flex-shrink-0 flex-grow-0">
          <PathHistoryControls />
        </div>
        <div className="flex-1 pr-2 min-w-0">
          <PathBar />
        </div>
        <SearchBar />
      </div>

      {children}
    </div>
  );
}


================================================
FILE: app/components/NewDocument.tsx
================================================
import { DragAndDropForm } from "./DragAndDropForm";
import { Title } from "./Primitives/Title";
import { SampleUrls } from "./SampleUrls";
import { UrlForm } from "./UrlForm";

export function NewDocument() {
  return (
    <div className="bg-indigo-700 text-white rounded-sm shadow-md w-96 max-w-max p-3 transition">
      <div className="flex flex-col">
        <UrlForm className="mb-2" />
        <DragAndDropForm />

        <div className="mt-4">
          <Title className="mb-2 text-slate-200">No JSON? Try it out:</Title>
          <SampleUrls />
        </div>
      </div>
    </div>
  );
}


================================================
FILE: app/components/NewFile.tsx
================================================
import { DragAndDropForm } from "./DragAndDropForm";
import { Title } from "./Primitives/Title";
import { SampleUrls } from "./SampleUrls";
import { UrlForm } from "./UrlForm";

export function NewFile() {
  return (
    <div>
      <div className="mb-4">
        <UrlForm />
      </div>
      <DragAndDropForm />

      <div className="mt-4 pt-5">
        <Title className="mb-2 text-slate-200">No JSON? Try it out:</Title>
        <SampleUrls />
      </div>
    </div>
  );
}


================================================
FILE: app/components/OpenInWindow.tsx
================================================
export type OpenInNewWindowProps = {
  children?: React.ReactNode;
  url?: string;
  className?: string;
};

export function OpenInNewWindow({
  url,
  className,
  children,
}: OpenInNewWindowProps) {
  return (
    <a href={url} target="_blank" className={`${className} relative`}>
      {children}
    </a>
  );
}


================================================
FILE: app/components/PathBar.tsx
================================================
import {
  ChevronRightIcon,
  ArrowLeftIcon,
  ArrowRightIcon,
  PencilAltIcon,
  CheckIcon,
} from "@heroicons/react/outline";
import { ColumnViewNode } from "~/useColumnView";
import { Body } from "./Primitives/Body";
import {
  useJsonColumnViewAPI,
  useJsonColumnViewState,
} from "../hooks/useJsonColumnView";
import { useHotkeys } from "react-hotkeys-hook";
import { memo, useEffect, useRef, useState } from "react";
import { useJson } from '~/hooks/useJson';
import { JSONHeroPath } from '@jsonhero/path';

export function PathBar() {
  const [isEditable, setIsEditable] = useState(false);
  const { selectedNodes, highlightedNodeId } = useJsonColumnViewState();
  const { goToNodeId } = useJsonColumnViewAPI();
  const [json] = useJson();

  if (isEditable) {
    return (
      <PathBarText
        selectedNodes={selectedNodes}
        onConfirm={(newPath) => {
          setIsEditable(false);
          const heroPath = new JSONHeroPath(newPath);
          const node = heroPath.first(json);
          if (node) {
            goToNodeId(newPath, 'pathBar');
          }
        }}
      />
    );
  }

  return (
    <PathBarLink
      selectedNodes={selectedNodes}
      highlightedNodeId={highlightedNodeId}
      enableEdit={() => setIsEditable(true)}
    />
  );
}

export function PathBarText({ selectedNodes, onConfirm }: { selectedNodes: ColumnViewNode[], onConfirm: (newPath: string) => void; }) {
  const [path, setPath] = useState('');
  const ref = useRef<HTMLInputElement>(null);

  useEffect(() => {
    setPath(selectedNodes.at(-1)?.id || '');
  }, [selectedNodes]);

  useEffect(() => {
    if (ref.current) {
      ref.current.focus();
    }
  }, [ref]);

  return (
    <form
      onSubmit={(e) => {
        onConfirm(path);
        e.preventDefault();
      }}
      // onBlur={() => onConfirm(path)}
      className="flex overflow-x-hidden items-center bg-slate-300 dark:bg-slate-700 rounded-sm"
    >
      <label className="grow">
        <input
          ref={ref}
          className={
            "w-full border-none outline-none text-ellipsis text-base px-2 py-0 rounded-sm bg-transparent dark:text-slate-200"
          }
          style={{ boxShadow: 'none' }}
          type="text"
          name="title"
          spellCheck="false"
          placeholder="Name your JSON file"
          value={path}
          onChange={(e) => setPath(e.target.value)}
        />
      </label>
      <button type="submit" className="flex ml-auto justify-center items-center w-[26px] h-[26px] hover:bg-slate-400 dark:text-slate-400 dark:hover:bg-white dark:hover:bg-opacity-[10%]">
        <CheckIcon className='h-5' />
      </button>
    </form>
  );
}

export type PathBarLinkProps = {
  selectedNodes: ColumnViewNode[];
  highlightedNodeId?: string;
  enableEdit: () => void;
};

export function PathBarLink({
  selectedNodes,
  highlightedNodeId,
  enableEdit,
}: PathBarLinkProps) {
  const { goToNodeId } = useJsonColumnViewAPI();

  return (
    <div
      className="flex flex-shrink-0 flex-grow-0 overflow-x-hidden"
      onClick={(event) => {
        if (event.detail == 2) {
          enableEdit();
        }
      }}
    >
      {selectedNodes.map((node, index) => {
        return (
          <PathBarItem
            key={index}
            node={node}
            isHighlighted={highlightedNodeId === node.id}
            onClick={(id) => goToNodeId(id, "pathBar")}
            isLast={index == selectedNodes.length - 1}
          />
        );
      })}
      <button
        className="flex ml-auto justify-center items-center w-[26px] h-[26px] hover:bg-slate-300 dark:text-slate-400 dark:hover:bg-white dark:hover:bg-opacity-[10%]"
        onClick={enableEdit}>
        <PencilAltIcon className='h-5' />
      </button>
    </div>
  );
}

export function PathHistoryControls() {
  const { canGoBack, canGoForward } = useJsonColumnViewState();
  const { goBack, goForward } = useJsonColumnViewAPI();

  useHotkeys(
    "[",
    () => {
      goBack();
    },
    [goBack]
  );

  useHotkeys(
    "]",
    () => {
      goForward();
    },
    [goForward]
  );

  return (
    <div className="flex h-full">
      <button
        className="flex justify-center items-center w-[26px] h-[26px] disabled:text-slate-400 disabled:text-opacity-50 text-slate-700 hover:bg-slate-300 hover:disabled:bg-transparent rounded-sm transition dark:disabled:text-slate-700 dark:text-slate-400 dark:hover:bg-white dark:hover:bg-opacity-[5%] dark:hover:disabled:bg-transparent"
        disabled={!canGoBack}
        onClick={goBack}
      >
        <ArrowLeftIcon className="w-5 h-6" />
      </button>
      <button
        className="flex justify-center items-center w-[26px] h-[26px] disabled:text-slate-400 disabled:text-opacity-50 text-slate-700 hover:bg-slate-300 hover:disabled:bg-transparent rounded-sm transition dark:disabled:text-slate-700 dark:text-slate-400 dark:hover:bg-white dark:hover:bg-opacity-[5%] dark:hover:disabled:bg-transparent"
        disabled={!canGoForward}
        onClick={goForward}
      >
        <ArrowRightIcon className="w-5 h-6" />
      </button>
    </div>
  );
}

function PathBarElement({
  node,
  isHighlighted,
  onClick,
  isLast,
}: {
  node: ColumnViewNode;
  isHighlighted: boolean;
  onClick?: (id: string) => void;
  isLast: boolean;
}) {
  return (
    <div
      className="flex items-center min-w-0"
      style={{
        flexShrink: 1,
      }}
    >
      <div
        className={`flex items-center hover:cursor-pointer min-w-0 transition ${isHighlighted
          ? "text-slate-700 bg-slate-300 px-2 py-[3px] rounded-sm dark:text-white dark:bg-slate-700"
          : "hover:bg-slate-300 px-2 py-[3px] rounded-sm transition dark:hover:bg-white dark:hover:bg-opacity-[5%]"
          }`}
        style={{
          flexShrink: 1,
        }}
        onClick={() => onClick && onClick(node.id)}
      >
        <div className="w-4 flex-shrink-[0.5] flex-grow-0 flex-col justify-items-center whitespace-nowrap overflow-x-hidden transition dark:text-slate-400">
          {node.icon && <node.icon className="h-3 w-3" />}
        </div>
        <Body className="flex-shrink flex-grow-0 whitespace-nowrap overflow-x-hidden text-ellipsis transition dark:text-slate-400">
          {node.title}
        </Body>
      </div>

      {isLast ? (
        <></>
      ) : (
        <ChevronRightIcon className="flex-grow-0 flex-shrink-[0.5] w-4 h-4 text-slate-400 whitespace-nowrap overflow-x-hidden" />
      )}
    </div>
  );
}

const PathBarItem = memo(PathBarElement);


================================================
FILE: app/components/PathPreview.tsx
================================================
import { ChevronRightIcon, EyeIcon } from "@heroicons/react/outline";
import { useMemo } from "react";
import { useJsonColumnViewAPI } from "~/hooks/useJsonColumnView";
import { ColumnViewNode, IconComponent } from "~/useColumnView";
import { Body } from "./Primitives/Body";

import eyeIcon from "~/assets/svgs/EyeIcon.svg";

export type PathPreviewProps = {
  nodes: ColumnViewNode[];
  maxComponents?: number;
  enabled?: boolean;
};

type ValueComponent = {
  type: "value";
  id: string;
  title: string;
  icon?: IconComponent;
};

type EllipsisComponent = {
  type: "ellipsis";
  id: "ellipsis";
};

type Component = ValueComponent | EllipsisComponent;

export function PathPreview({
  nodes,
  maxComponents,
  enabled,
}: PathPreviewProps) {
  const isEnabled = useMemo(() => {
    if (enabled === undefined) {
      return true;
    }
    return enabled;
  }, [enabled]);

  const { goToNodeId } = useJsonColumnViewAPI();

  const components = useMemo<Array<Component>>(() => {
    if (maxComponents == null || nodes.length <= maxComponents) {
      return nodes.map((n) => {
        return { type: "value", id: n.id, title: n.title, icon: n.icon };
      });
    }

    let components = Array<Component>();

    //add the elements up to the ellipsis
    for (let index = 0; index < maxComponents - 1; index++) {
      const node = nodes[index];
      components.push({
        type: "value",
        id: node.id,
        title: node.title,
        icon: node.icon,
      });
    }

    //add ellipsis
    components.push({ type: "ellipsis", id: "ellipsis" });

    //add final element
    const lastNode = nodes[nodes.length - 1];
    components.push({
      type: "value",
      id: lastNode.id,
      title: lastNode.title,
      icon: lastNode.icon,
    });

    return components;
  }, [nodes, maxComponents]);

  return (
    <div
      className={`flex select-none pl-7 ${
        isEnabled
          ? `relative transition hover:bg-slate-200 hover:cursor-pointer dark:hover:bg-slate-600 after:transition after:absolute after:h-3 after:w-3 after:opacity-0 hover:after:opacity-100 after:top-1 after:left-1 after:content-[''] after:bg-[url('${eyeIcon}')] after:bg-no-repeat`
          : "disabled"
      }`}
      onClick={() =>
        isEnabled &&
        goToNodeId(components[components.length - 1].id, "relatedValues")
      }
    >
      <div
        className={`flex rounded-sm px-2 ${
          isEnabled
            ? ""
            : "hover:bg-slate-100 hover:cursor-pointer dark:hover:bg-slate-600"
        }`}
      >
        {components.map((node, index) => {
          if (node.type === "ellipsis") {
            return (
              <div
                key={node.id}
                className="flex flex-none items-center min-w-0"
              >
                <div className="flex-none text-md">…</div>
                <ChevronRightIcon className="flex-none w-4 h-4 text-slate-400 whitespace-nowrap overflow-x-hidden" />
              </div>
            );
          } else {
            return (
              <div className="flex items-center min-w-0" key={node.id}>
                <div className="flex items-center min-w-0">
                  <div className="w-4 flex-shrink-[0.5] flex-grow-0 flex-col justify-items-center whitespace-nowrap overflow-x-hidden transition dark:text-slate-300">
                    {node.icon && <node.icon className="h-3 w-3" />}
                  </div>
                  <Body className="flex-shrink flex-grow-0 whitespace-nowrap overflow-x-hidden text-ellipsis transition dark:text-slate-300">
                    {node.title}
                  </Body>
                </div>

                {index == components.length - 1 ? (
                  <></>
                ) : (
                  <ChevronRightIcon className="flex-grow-0 flex-shrink-[0.5] w-4 h-4 text-slate-400 whitespace-nowrap overflow-x-hidden" />
                )}
              </div>
            );
          }
        })}
      </div>
    </div>
  );
}


================================================
FILE: app/components/PreferencesProvider.tsx
================================================
import {createContext, Dispatch, ReactNode, SetStateAction, useContext, useEffect, useState} from 'react';

interface Preferences {
  indent: number;
}

const PreferencesDefaults: Preferences = {
  indent: 2,
};

type PreferencesContextType = [
  Preferences | undefined,
  Dispatch<SetStateAction<Preferences | undefined>>
];

const PreferencesContext = createContext<PreferencesContextType | undefined>(undefined);

const loadPreferences = (): Preferences => {
  const savedPreferences = localStorage.getItem('preferences');
  const parsedPreferences = JSON.parse(savedPreferences || '{}');
  for (const [key, value] of Object.entries(PreferencesDefaults)) {
    if (!parsedPreferences[key]) parsedPreferences[key] = value;
  }
  return parsedPreferences;
};
const savePreferences = (preferences: Preferences) => localStorage.setItem('preferences', JSON.stringify(preferences));

export function PreferencesProvider({
  children,
}: {
  children: ReactNode;
}) {
  const [preferences, setPreferences] = useState<Preferences>();

  useEffect(() => {
    const preferences = loadPreferences();
    setPreferences(preferences);
  }, []);

  useEffect(() => {
    if (preferences === undefined) return;
    savePreferences(preferences);
  }, [preferences]);

  return (
    <PreferencesContext.Provider value={[preferences, setPreferences]}>
      {children}
    </PreferencesContext.Provider>
  );
}

export function usePreferences() {
  const context = useContext(PreferencesContext);
  if (context === undefined) {
    throw new Error('usePreferences must be used within a PreferencesProvider');
  }
  return context;
}

================================================
FILE: app/components/Preview/CalendarMonth.tsx
================================================
import { useMemo } from "react";
import { Title } from "../Primitives/Title";

export type CalendarMonthProps = {
  date: Date;
};

type Day = {
  date: string;
  isCurrentMonth: boolean;
  isHighlighted: boolean;
};

function dateString(date: Date): string {
  return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
}

function isSameDay(date: Date, otherDate: Date): boolean {
  return (
    date.getFullYear() === otherDate.getFullYear() &&
    date.getMonth() === otherDate.getMonth() &&
    date.getDate() === otherDate.getDate()
  );
}

export function CalendarMonth({ date }: CalendarMonthProps) {
  const days = useMemo<Array<Day>>(() => {
    let days: Array<Day> = [];

    //create first day of the month
    const firstDayOfMonth = new Date(date);
    firstDayOfMonth.setDate(1);

    //if the first day isn't a monday, we need to add days from the previous month in
    const firstDayOfWeek = firstDayOfMonth.getDay() - 1;
    if (firstDayOfWeek != 0) {
      const previousMonthDate = new Date(firstDayOfMonth);
      for (let index = 0; index < firstDayOfWeek; index++) {
        previousMonthDate.setDate(previousMonthDate.getDate() - 1);
        days.push({
          date: dateString(previousMonthDate),
          isCurrentMonth: false,
          isHighlighted: isSameDay(date, previousMonthDate),
        });
      }
    }

    //current month
    let currentMonthDate = new Date(firstDayOfMonth);
    const monthNumber = firstDayOfMonth.getMonth();
    while (true) {
      days.push({
        date: dateString(currentMonthDate),
        isCurrentMonth: true,
        isHighlighted: isSameDay(date, currentMonthDate),
      });

      currentMonthDate.setDate(currentMonthDate.getDate() + 1);

      if (currentMonthDate.getMonth() !== monthNumber) {
        break;
      }
    }

    //next month
    const lastDayOfMonthDayOfWeek = currentMonthDate.getDay() - 1;
    const nextMonthDayCount = 7 - lastDayOfMonthDayOfWeek;
    for (let index = 0; index < nextMonthDayCount; index++) {
      days.push({
        date: dateString(currentMonthDate),
        isCurrentMonth: false,
        isHighlighted: isSameDay(date, currentMonthDate),
      });
      currentMonthDate.setDate(currentMonthDate.getDate() + 1);
    }

    return days;
  }, [date]);

  return (
    <section className="">
      <Title className="text-left text-slate-800 dark:text-slate-400">
        {new Intl.DateTimeFormat("en-US", {
          weekday: "short",
          year: "numeric",
          month: "short",
          day: "numeric",
          hour: "numeric",
          hour12: true,
          minute: "2-digit",
          second: "2-digit",
          timeZoneName: "short",
        }).format(date)}
      </Title>
      <div className="uppercase mt-2 grid text-center tracking-wider grid-cols-7 text-sm leading-6 text-gray-500 dark:text-slate-500">
        <div>Mon</div>
        <div>Tue</div>
        <div>Wed</div>
        <div>Thu</div>
        <div>Fri</div>
        <div>Sat</div>
        <div>Sun</div>
      </div>
      <div className="isolate mt-2 grid grid-cols-7 gap-px rounded-lg bg-gray-200 text-sm ring-1 cursor-default ring-slate-200 dark:ring-slate-600 dark:bg-slate-600">
        {days.map((day, dayIdx) => (
          <button
            key={day.date}
            type="button"
            className={`"cursor-default" ${classNames(
              day.isCurrentMonth
                ? "bg-white text-slate-900 dark:text-slate-300 dark:bg-slate-800"
                : "bg-slate-100 text-slate-400 dark:text-slate-500 dark:bg-slate-800 dark:bg-opacity-90",
              dayIdx === 0 && "rounded-tl-lg",
              dayIdx === 6 && "rounded-tr-lg",
              dayIdx === days.length - 7 && "rounded-bl-lg",
              dayIdx === days.length - 1 && "rounded-br-lg",
              "relative py-1.5 focus:z-10"
            )}`}
          >
            <time
              dateTime={day.date}
              className={classNames(
                day.isHighlighted && "bg-indigo-600 font-semibold text-white",
                "mx-auto flex h-7 w-7 items-center cursor-default justify-center rounded-full"
              )}
            >
              {day.date.split("-").pop()?.replace(/^0/, "")}
            </time>
          </button>
        ))}
      </div>
    </section>
  );
}

function classNames(...classes: (string | boolean)[]) {
  return classes.filter(Boolean).join(" ");
}


================================================
FILE: app/components/Preview/PreviewBox.tsx
================================================
import { useCallback } from "react";
import { Title } from "../Primitives/Title";

export type PreviewBoxProps = {
  link?: string;
  children: React.ReactNode;
  className?: string;
};

export function PreviewBox({ link, className, children }: PreviewBoxProps) {
  const onClick = useCallback(() => {
    if (!link) return;
    window.open(link, "_blank");
  }, [link]);

  return (
    <div className={className}>
      <Title className="text-slate-700 transition dark:text-slate-400 mb-2">
        Preview
      </Title>
      <div
        onClick={onClick}
        className="block rounded-sm p-2 text-slate-700 bg-slate-100 transition dark:text-slate-300 dark:bg-white dark:bg-opacity-5 hover:cursor-pointer"
      >
        <div>{children}</div>
      </div>
    </div>
  );
}


================================================
FILE: app/components/Preview/PreviewProperties.tsx
================================================
import { Body } from "../Primitives/Body";

export type PreviewPropertyProps = {
  properties: Array<PreviewProperty>;
};

export type PreviewProperty = {
  key: string;
  title: string;
  icon?: JSX.Element;
};

export function PreviewProperties({ properties }: PreviewPropertyProps) {
  return (
    <div className="-mb-1">
      {properties.map((property) => {
        return (
          <Body
            className="text-slate-500 mr-2 inline-flex items-center"
            key={property.key}
          >
            {property.icon && (
              <span className="w-4 h-4 inline-block text-slate-500 mr-1 flex-none">
                {property.icon}
              </span>
            )}
            <span>{property.title}</span>
          </Body>
        );
      })}
    </div>
  );
}


================================================
FILE: app/components/Preview/PreviewValue.tsx
================================================
import { useSelectedInfo } from "~/hooks/useSelectedInfo";
import { PreviewString } from "./Types/PreviewString";

export function PreviewValue() {
  const info = useSelectedInfo();

  if (!info) {
    return <></>;
  }

  switch (info.name) {
    case "string":
      return <PreviewString info={info} />;
    default:
      return <></>;
  }
}


================================================
FILE: app/components/Preview/Types/PreviewAudioUri.tsx
================================================
import { useRef } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { Body } from "~/components/Primitives/Body";
import { PreviewBox } from "../PreviewBox";

export function PreviewAudioUri({
  src,
  contentType,
}: {
  src: string;
  contentType: string;
}) {
  const mediaRef = useRef<HTMLMediaElement>(null);

  useHotkeys(
    "space",
    (e) => {
      e.preventDefault();

      if (mediaRef.current) {
        if (mediaRef.current.paused) {
          mediaRef.current.play();
        } else {
          mediaRef.current.pause();
        }
      }
    },
    [mediaRef]
  );

  return (
    <div>
      <PreviewBox>
        <Body>
          <audio controls src={src} ref={mediaRef}>
            Sorry, your browser doesn't support embedded audio.
          </audio>
        </Body>
      </PreviewBox>
    </div>
  );
}


================================================
FILE: app/components/Preview/Types/PreviewDate.tsx
================================================
import { Temporal } from "@js-temporal/polyfill";
import { JSONDateTimeFormat, JSONStringType } from "@jsonhero/json-infer-types";
import { useMemo } from "react";
import { inferTemporal } from "~/utilities/inferredTemporal";
import { CalendarMonth } from "../CalendarMonth";

export type PreviewDateProps = {
  value: string;
  format: JSONDateTimeFormat;
};

export function PreviewDate({ value, format }: PreviewDateProps) {
  const temporal = inferTemporal(value, format);

  if (!temporal) {
    return <></>;
  }

  // Can only convert to the legacy Date class if temporal is either a ZonedDateTime or an Instant
  if ("epochMilliseconds" in temporal) {
    const date = new Date(temporal.epochMilliseconds);

    return <CalendarMonth date={date} />;
  } else if (temporal instanceof Temporal.PlainDate) {
    const date = new Date(temporal.year, temporal.month - 1, temporal.day);

    return <CalendarMonth date={date} />;
  } else if (temporal instanceof Temporal.PlainDateTime) {
    const date = new Date(
      temporal.year,
      temporal.month - 1,
      temporal.day,
      temporal.hour,
      temporal.minute,
      temporal.second,
      temporal.millisecond
    );

    return <CalendarMonth date={date} />;
  } else {
    return <></>;
  }
}


================================================
FILE: app/components/Preview/Types/PreviewHtml.tsx
================================================
import { Body } from "~/components/Primitives/Body";
import { Title } from "~/components/Primitives/Title";
import { PreviewBox } from "../PreviewBox";
import { PreviewHtml } from "./preview.types";

export type PreviewHtmlProps = {
  info: PreviewHtml;
};

export function PreviewHtml({ info }: PreviewHtmlProps) {
  return (
    <PreviewBox link={info.url}>
      <div>
        {info.title && (
          <Title>
            {info.icon && (
              <img src={info.icon.url} className="w-4 h-4 inline mr-1" alt="" />
            )}
            <span className="inline">{info.title}</span>
          </Title>
        )}
        {info.description && <Body>{info.description}</Body>}
      </div>
      {info.image && (
        <div>
          <img className="block" src={info.image?.url} alt={info.image?.alt} />
        </div>
      )}
    </PreviewBox>
  );
}


================================================
FILE: app/components/Preview/Types/PreviewIPFSImage.tsx
================================================
import { PreviewImageUri } from "./PreviewImageUri";

export function PreviewIPFSImage({ src }: { src: URL }) {
  const newSrc = createPreviewIPFSImageURL(src);

  return <PreviewImageUri src={newSrc} />;
}

const createPreviewIPFSImageURL = (src: URL): string => {
  const url = new URL(src.href);

  url.protocol = "https:";
  url.pathname = `/ipfs/${src.pathname.substring(1)}`;
  url.hostname = "ipfs.io";
  // Remove the leading slash

  return url.href;
};


================================================
FILE: app/components/Preview/Types/PreviewImage.tsx
================================================
import { formatBytes } from "~/utilities/formatter";
import { PreviewBox } from "../PreviewBox";
import { PreviewProperties, PreviewProperty } from "../PreviewProperties";
import { PreviewImage } from "./preview.types";

export type PreviewImageProps = {
  info: PreviewImage;
};

export function PreviewImage({ info }: PreviewImageProps) {
  let properties: Array<PreviewProperty> = [];

  if (info.mimeType) {
    properties.push({ key: "mimeType", title: info.mimeType });
  }

  if (info.size) {
    properties.push({ key: "fileSize", title: `${formatBytes(info.size)}` });
  }

  const src = info.image ? info.image.url : info.url;

  return (
    <PreviewBox link={info.url}>
      <img className="block max-h-96 w-full object-contain" src={src} />
      <PreviewProperties properties={properties} />
    </PreviewBox>
  );
}


================================================
FILE: app/components/Preview/Types/PreviewImageUri.tsx
================================================
import { Body } from "~/components/Primitives/Body";
import { PreviewBox } from "../PreviewBox";

export function PreviewImageUri({
  src,
  contentType,
  alt = "",
}: {
  src: string;
  contentType?: string;
  alt?: string
}) {
  return (
    <div>
      <PreviewBox link={src}>
        <Body>
          <img src={src} alt={alt} />
        </Body>
      </PreviewBox>
    </div>
  );
}


================================================
FILE: app/components/Preview/Types/PreviewJson.tsx
================================================
import { useState } from "react";
import { CodeViewer } from "~/components/CodeViewer";
import { CopyTextButton } from "~/components/CopyTextButton";
import { OpenInNewWindow } from "~/components/OpenInWindow";
import { Body } from "~/components/Primitives/Body";
import { PreviewBox } from "../PreviewBox";
import { PreviewJson } from "./preview.types";

export function PreviewJson({ preview }: { preview: PreviewJson }) {
  const [hovering, setHovering] = useState(false);
  const jsonHeroUrl = new URL(
    `/actions/createFromUrl?jsonUrl=${encodeURIComponent(preview.url)}`,
    window.location.origin
  );

  jsonHeroUrl.searchParams.append("utm_source", "preview");

  const code = JSON.stringify(preview.json, null, 2);

  return (
    <PreviewBox className="relative">
      <div
        className="relative w-full h-full"
        onMouseEnter={() => setHovering(true)}
        onMouseLeave={() => setHovering(false)}
      >
        <CodeViewer code={code} />
        <div
          className={`absolute top-0 flex justify-end pt-1 pr-1 w-full transition ${
            hovering ? "opacity-100" : "opacity-0"
          }`}
        >
          <CopyTextButton
            value={code}
            className="bg-slate-200 hover:bg-slate-300 h-fit mr-1 px-2 py-0.5 rounded-sm transition dark:text-white dark:bg-slate-700 dark:hover:bg-slate-600"
          ></CopyTextButton>
          <OpenInNewWindow
            url={jsonHeroUrl.href}
            className="bg-slate-200 hover:bg-slate-300 h-fit px-2 py-0.5 rounded-sm transition dark:text-white dark:bg-slate-700 dark:hover:bg-slate-600"
          >
            <Body>Open in tab</Body>
          </OpenInNewWindow>
        </div>
      </div>
    </PreviewBox>
  );
}


=====================
Download .txt
gitextract_7rs4w8rw/

├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .vscode/
│   ├── launch.json
│   └── tasks.json
├── CONTRIBUTING.md
├── DEVELOPMENT.md
├── Dockerfile
├── LICENSE
├── README.md
├── SELF_HOSTING.md
├── app/
│   ├── bindings.d.ts
│   ├── components/
│   │   ├── AutoplayVideo.tsx
│   │   ├── BlankColumn.tsx
│   │   ├── CodeEditor.tsx
│   │   ├── CodeViewer.tsx
│   │   ├── Column.tsx
│   │   ├── ColumnItem.tsx
│   │   ├── Columns.tsx
│   │   ├── ContainerInfo.tsx
│   │   ├── CopySelectedNode.tsx
│   │   ├── CopyText.tsx
│   │   ├── CopyTextButton.tsx
│   │   ├── DataTable.tsx
│   │   ├── DocumentTitle.tsx
│   │   ├── DragAndDropForm.tsx
│   │   ├── ExampleDoc.tsx
│   │   ├── ExampleUrl.tsx
│   │   ├── FileSelector/
│   │   │   └── FileDropzone.tsx
│   │   ├── Footer.tsx
│   │   ├── Header.tsx
│   │   ├── Home/
│   │   │   ├── HomeApiHeroBanner.tsx
│   │   │   ├── HomeApiHeroLaptop.tsx
│   │   │   ├── HomeCollaborateSection.tsx
│   │   │   ├── HomeEdgeCasesSection.tsx
│   │   │   ├── HomeFeatureGridSection.tsx
│   │   │   ├── HomeFooter.tsx
│   │   │   ├── HomeGithubBanner.tsx
│   │   │   ├── HomeGridFeatureItem.tsx
│   │   │   ├── HomeHeader.tsx
│   │   │   ├── HomeHeroSection.tsx
│   │   │   ├── HomeInfoBoxSection.tsx
│   │   │   ├── HomeSearchSection.tsx
│   │   │   ├── HomeSection.tsx
│   │   │   └── HomeSplitSection.tsx
│   │   ├── Icons/
│   │   │   ├── ArrayIcon.tsx
│   │   │   ├── ArrowKeysIcon.tsx
│   │   │   ├── ArrowKeysUpDownIcon.tsx
│   │   │   ├── CopyShortcutIcon.tsx
│   │   │   ├── DiscordIcon.tsx
│   │   │   ├── DiscordIconTransparent.tsx
│   │   │   ├── EmailIcon.tsx
│   │   │   ├── EmailIconTransparent.tsx
│   │   │   ├── EscapeKeyIcon.tsx
│   │   │   ├── GithubIcon.tsx
│   │   │   ├── GithubIconSimple.tsx
│   │   │   ├── LoadingIcon.tsx
│   │   │   ├── Logo.tsx
│   │   │   ├── LogoTriggerdotdev.tsx
│   │   │   ├── MoonIcon.tsx
│   │   │   ├── ObjectIcon.tsx
│   │   │   ├── ShortcutIcon.tsx
│   │   │   ├── SquareBracketsIcon.tsx
│   │   │   ├── StringIcon.tsx
│   │   │   ├── SunIcon.tsx
│   │   │   ├── TreeIcon.tsx
│   │   │   └── TwitterIcon.tsx
│   │   ├── IndentPreference.tsx
│   │   ├── InfoHeader.tsx
│   │   ├── InfoPanel.tsx
│   │   ├── JsonColumnView.tsx
│   │   ├── JsonEditor.tsx
│   │   ├── JsonPreview.tsx
│   │   ├── JsonSchemaViewer.tsx
│   │   ├── JsonTreeView.tsx
│   │   ├── JsonView.tsx
│   │   ├── NewDocument.tsx
│   │   ├── NewFile.tsx
│   │   ├── OpenInWindow.tsx
│   │   ├── PathBar.tsx
│   │   ├── PathPreview.tsx
│   │   ├── PreferencesProvider.tsx
│   │   ├── Preview/
│   │   │   ├── CalendarMonth.tsx
│   │   │   ├── PreviewBox.tsx
│   │   │   ├── PreviewProperties.tsx
│   │   │   ├── PreviewValue.tsx
│   │   │   └── Types/
│   │   │       ├── PreviewAudioUri.tsx
│   │   │       ├── PreviewDate.tsx
│   │   │       ├── PreviewHtml.tsx
│   │   │       ├── PreviewIPFSImage.tsx
│   │   │       ├── PreviewImage.tsx
│   │   │       ├── PreviewImageUri.tsx
│   │   │       ├── PreviewJson.tsx
│   │   │       ├── PreviewString.tsx
│   │   │       ├── PreviewUri.tsx
│   │   │       ├── PreviewUriElement.tsx
│   │   │       ├── PreviewVideoUri.tsx
│   │   │       ├── RetweetIcon.tsx
│   │   │       └── preview.types.d.ts
│   │   ├── Primitives/
│   │   │   ├── Body.tsx
│   │   │   ├── BodyBold.tsx
│   │   │   ├── ExtraLargeTitle.tsx
│   │   │   ├── LargeMono.tsx
│   │   │   ├── LargeTitle.tsx
│   │   │   ├── Mono.tsx
│   │   │   ├── PageNotFoundTitle.tsx
│   │   │   ├── SmallBody.tsx
│   │   │   ├── SmallSubtitle.tsx
│   │   │   ├── SmallTitle.tsx
│   │   │   └── Title.tsx
│   │   ├── Properties/
│   │   │   ├── PropertiesFloat.tsx
│   │   │   ├── PropertiesInt.tsx
│   │   │   ├── PropertiesString.tsx
│   │   │   └── PropertiesValue.tsx
│   │   ├── RelatedValues.tsx
│   │   ├── Resizable.tsx
│   │   ├── SampleUrls.tsx
│   │   ├── SearchBar.tsx
│   │   ├── SearchPalette.tsx
│   │   ├── Share.tsx
│   │   ├── SideBar.tsx
│   │   ├── StarCountProvider.tsx
│   │   ├── ThemeModeToggle.tsx
│   │   ├── ThemeProvider.tsx
│   │   ├── ToolTip.tsx
│   │   ├── UI/
│   │   │   ├── Dialog.tsx
│   │   │   ├── GithubStar.tsx
│   │   │   ├── GithubStarSmall.tsx
│   │   │   ├── Popover.tsx
│   │   │   ├── Tabs.tsx
│   │   │   └── ToastPopover.tsx
│   │   ├── UrlForm.tsx
│   │   ├── ValueIcon.tsx
│   │   └── json-schema-map.d.ts
│   ├── entry.client.tsx
│   ├── entry.server.tsx
│   ├── entry.worker.ts
│   ├── graphJSON.server.ts
│   ├── hooks/
│   │   ├── useClickOutside.tsx
│   │   ├── useIsMounted.tsx
│   │   ├── useJson.tsx
│   │   ├── useJsonColumnView.tsx
│   │   ├── useJsonDoc.tsx
│   │   ├── useJsonSchema.tsx
│   │   ├── useJsonSearch.tsx
│   │   ├── useJsonTree.tsx
│   │   ├── useLoadWhenOnline.tsx
│   │   ├── useMemoCompare.ts
│   │   ├── useOnScreen.tsx
│   │   ├── useRelatedPaths.ts
│   │   ├── useSelectedInfo.tsx
│   │   └── useVirtualTree.ts
│   ├── jsonDoc.server.ts
│   ├── root.tsx
│   ├── routes/
│   │   ├── actions/
│   │   │   ├── $id/
│   │   │   │   └── update.ts
│   │   │   ├── createFromFile.ts
│   │   │   ├── createFromUrl.ts
│   │   │   ├── getPreview.$url.ts
│   │   │   └── setTheme.ts
│   │   ├── api/
│   │   │   └── create[.json].ts
│   │   ├── index.tsx
│   │   ├── j/
│   │   │   ├── $id/
│   │   │   │   ├── editor.tsx
│   │   │   │   ├── index.tsx
│   │   │   │   ├── terminal.tsx
│   │   │   │   └── tree.tsx
│   │   │   ├── $id.tsx
│   │   │   └── $id[.json].ts
│   │   ├── new.tsx
│   │   └── privacy.mdx
│   ├── services/
│   │   ├── apihero.server.ts
│   │   ├── github.server.ts
│   │   ├── toast.server.ts
│   │   └── uriPreview.server.ts
│   ├── theme.server.ts
│   ├── useColumnView/
│   │   └── index.ts
│   └── utilities/
│       ├── animationConstants.ts
│       ├── classnames.ts
│       ├── codeMirrorSetup.ts
│       ├── codeMirrorTheme.ts
│       ├── colors.ts
│       ├── dataType.ts
│       ├── formatStarCount.ts
│       ├── formatter.ts
│       ├── getRandomUserAgent.ts
│       ├── icons.ts
│       ├── inferredTemporal.ts
│       ├── jsonColumnView.ts
│       ├── nullable.ts
│       ├── relatedValues.ts
│       ├── safeFetch.ts
│       ├── search.ts
│       ├── stableJson.ts
│       └── xml/
│           ├── __test__/
│           │   ├── convertXmlToJsonString.test.ts
│           │   ├── isXML.test.ts
│           │   └── xml.txt
│           ├── convertFromRawXml.ts
│           ├── createFromRawXml.ts
│           └── isXML.ts
├── examples/
│   ├── owenWilsonWows.json
│   ├── pokemon.json
│   └── ronSwansonQuotes.json
├── package.json
├── remix.config.js
├── remix.env.d.ts
├── styles/
│   └── tailwind.css
├── tailwind.config.js
├── tests/
│   ├── formatStarCount.test.ts
│   ├── jsonColumnView.test.ts
│   ├── relatedValues.test.ts
│   ├── search.test.ts
│   ├── setup.js
│   └── stableJson.test.ts
├── tsconfig.json
├── worker/
│   └── index.js
├── wrangler.toml
└── wrangler.toml.dev
Download .txt
SYMBOL INDEX (434 symbols across 158 files)

FILE: app/components/AutoplayVideo.tsx
  function AutoplayVideo (line 4) | function AutoplayVideo({ src }: { src: string }) {

FILE: app/components/BlankColumn.tsx
  function BlankColumnElement (line 3) | function BlankColumnElement() {

FILE: app/components/CodeEditor.tsx
  type CodeEditorProps (line 15) | type CodeEditorProps = {
  type CodeEditorDefaultProps (line 28) | type CodeEditorDefaultProps = Required<
  function CodeEditor (line 38) | function CodeEditor(opts: CodeEditorProps) {

FILE: app/components/CodeViewer.tsx
  function CodeViewer (line 9) | function CodeViewer({ code, lang }: { code: string; lang?: "json" }) {

FILE: app/components/Column.tsx
  type ColumnProps (line 8) | type ColumnProps = {
  function ColumnElement (line 16) | function ColumnElement(column: ColumnProps) {

FILE: app/components/ColumnItem.tsx
  type ColumnItemProps (line 8) | type ColumnItemProps = {
  function ColumnItemElement (line 16) | function ColumnItemElement({

FILE: app/components/Columns.tsx
  function ColumnsElement (line 13) | function ColumnsElement({ columns }: { columns: ColumnDefinition[] }) {

FILE: app/components/ContainerInfo.tsx
  function ContainerInfo (line 15) | function ContainerInfo() {

FILE: app/components/CopySelectedNode.tsx
  function CopySelectedNodeShortcut (line 4) | function CopySelectedNodeShortcut() {

FILE: app/components/CopyText.tsx
  type CopyTextProps (line 3) | type CopyTextProps = {
  function CopyText (line 10) | function CopyText({

FILE: app/components/CopyTextButton.tsx
  type CopyTextButtonProps (line 6) | type CopyTextButtonProps = {
  function CopyTextButton (line 11) | function CopyTextButton({ value, className }: CopyTextButtonProps) {

FILE: app/components/DataTable.tsx
  type DataTableProps (line 5) | type DataTableProps = {
  type DataTableRow (line 9) | type DataTableRow = {
  type DataRowProps (line 15) | type DataRowProps = {

FILE: app/components/DocumentTitle.tsx
  function DocumentTitle (line 7) | function DocumentTitle() {

FILE: app/components/DragAndDropForm.tsx
  function DragAndDropForm (line 7) | function DragAndDropForm() {

FILE: app/components/ExampleDoc.tsx
  function ExampleDoc (line 3) | function ExampleDoc({

FILE: app/components/ExampleUrl.tsx
  function ExampleUrl (line 3) | function ExampleUrl({

FILE: app/components/Footer.tsx
  function Footer (line 15) | function Footer() {

FILE: app/components/Header.tsx
  function Header (line 19) | function Header() {

FILE: app/components/Home/HomeApiHeroBanner.tsx
  function HomeApiHeroBanner (line 4) | function HomeApiHeroBanner() {

FILE: app/components/Home/HomeApiHeroLaptop.tsx
  type IconProps (line 3) | type IconProps = {
  function HomeApiHeroLaptop (line 7) | function HomeApiHeroLaptop({ className }: IconProps) {

FILE: app/components/Home/HomeCollaborateSection.tsx
  function HomeCollaborateSection (line 8) | function HomeCollaborateSection() {

FILE: app/components/Home/HomeEdgeCasesSection.tsx
  function HomeEdgeCasesSection (line 8) | function HomeEdgeCasesSection() {

FILE: app/components/Home/HomeFeatureGridSection.tsx
  function HomeFeatureGridSection (line 14) | function HomeFeatureGridSection() {

FILE: app/components/Home/HomeFooter.tsx
  type HomeFooterProps (line 8) | type HomeFooterProps = {
  function HomeFooter (line 12) | function HomeFooter({ maxWidth = "1150px" }: HomeFooterProps) {

FILE: app/components/Home/HomeGithubBanner.tsx
  function GithubBanner (line 4) | function GithubBanner() {

FILE: app/components/Home/HomeGridFeatureItem.tsx
  type HomeGridFeatureItemProps (line 5) | type HomeGridFeatureItemProps = {
  function HomeGridFeatureItem (line 13) | function HomeGridFeatureItem(props: HomeGridFeatureItemProps) {

FILE: app/components/Home/HomeHeader.tsx
  function HomeHeader (line 16) | function HomeHeader({ fixed }: { fixed?: boolean }) {

FILE: app/components/Home/HomeHeroSection.tsx
  function HomeHeroSection (line 11) | function HomeHeroSection() {

FILE: app/components/Home/HomeInfoBoxSection.tsx
  function HomeInfoBoxSection (line 48) | function HomeInfoBoxSection() {
  function HomeInfoBoxSectionContent (line 56) | function HomeInfoBoxSectionContent() {
  function SampleJSONPreview (line 135) | function SampleJSONPreview({

FILE: app/components/Home/HomeSearchSection.tsx
  function HomeSearchSection (line 8) | function HomeSearchSection() {

FILE: app/components/Home/HomeSection.tsx
  type HomeSectionProps (line 1) | type HomeSectionProps = {
  function HomeSection (line 9) | function HomeSection({

FILE: app/components/Home/HomeSplitSection.tsx
  type HomeSplitSectionProps (line 3) | type HomeSplitSectionProps = {
  function HomeSplitSection (line 8) | function HomeSplitSection({
  function HomeSplitTextContent (line 21) | function HomeSplitTextContent({
  function HomeSplitMediaContent (line 33) | function HomeSplitMediaContent({

FILE: app/components/Icons/ArrayIcon.tsx
  function ArrayIcon (line 1) | function ArrayIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/ArrowKeysIcon.tsx
  function ArrowKeysIcon (line 1) | function ArrowKeysIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/ArrowKeysUpDownIcon.tsx
  function ArrowKeysUpDownIcon (line 1) | function ArrowKeysUpDownIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/CopyShortcutIcon.tsx
  function CopyShortcutIcon (line 1) | function CopyShortcutIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/DiscordIcon.tsx
  function DiscordIcon (line 1) | function DiscordIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/DiscordIconTransparent.tsx
  function DiscordIconTransparent (line 1) | function DiscordIconTransparent(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/EmailIcon.tsx
  function EmailIcon (line 1) | function EmailIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/EmailIconTransparent.tsx
  function EmailIconTransparent (line 1) | function EmailIconTransparent(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/EscapeKeyIcon.tsx
  function EscapeKeyIcon (line 1) | function EscapeKeyIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/GithubIcon.tsx
  function GithubIcon (line 1) | function GithubIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/GithubIconSimple.tsx
  type IconProps (line 1) | type IconProps = {
  function GithubIconSimple (line 5) | function GithubIconSimple({ className }: IconProps) {

FILE: app/components/Icons/LoadingIcon.tsx
  function LoadingIcon (line 1) | function LoadingIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/Logo.tsx
  function Logo (line 3) | function Logo({

FILE: app/components/Icons/LogoTriggerdotdev.tsx
  function LogoTriggerdotdev (line 1) | function LogoTriggerdotdev({

FILE: app/components/Icons/ObjectIcon.tsx
  function ObjectIcon (line 1) | function ObjectIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/ShortcutIcon.tsx
  type ShortcutIconProps (line 1) | type ShortcutIconProps = {
  function ShortcutIcon (line 6) | function ShortcutIcon({ className, children }: ShortcutIconProps) {

FILE: app/components/Icons/SquareBracketsIcon.tsx
  function SquareBracketsIcon (line 1) | function SquareBracketsIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/StringIcon.tsx
  function StringIcon (line 1) | function StringIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/TreeIcon.tsx
  function TreeIcon (line 1) | function TreeIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Icons/TwitterIcon.tsx
  function TwitterIcon (line 1) | function TwitterIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/IndentPreference.tsx
  constant MIN_INDENT (line 5) | const MIN_INDENT = 1;
  constant MAX_INDENT (line 6) | const MAX_INDENT = 8;
  function IndentPreference (line 8) | function IndentPreference() {

FILE: app/components/InfoHeader.tsx
  type InfoHeaderProps (line 18) | type InfoHeaderProps = {
  function InfoHeader (line 22) | function InfoHeader({ relatedPaths }: InfoHeaderProps) {
  function checkPathExists (line 112) | function checkPathExists(json: unknown, newPath: string) {
  function EmptyState (line 118) | function EmptyState() {

FILE: app/components/InfoPanel.tsx
  function InfoPanel (line 10) | function InfoPanel() {

FILE: app/components/JsonColumnView.tsx
  function JsonColumnView (line 9) | function JsonColumnView() {
  function KeyboardShortcuts (line 22) | function KeyboardShortcuts() {

FILE: app/components/JsonEditor.tsx
  function JsonEditor (line 13) | function JsonEditor() {

FILE: app/components/JsonPreview.tsx
  type JsonPreviewProps (line 21) | type JsonPreviewProps = {
  function JsonPreview (line 26) | function JsonPreview({ json, highlightPath }: JsonPreviewProps) {
  type LineRange (line 128) | interface LineRange {
  function highlightLineRange (line 142) | function highlightLineRange(range: LineRange | null) {
  function highlightLines (line 153) | function highlightLines(view: EditorView) {
  method constructor (line 174) | constructor(view: any) {
  method update (line 178) | update(update: { docChanged: any; viewportChanged: any; view: any }) {

FILE: app/components/JsonSchemaViewer.tsx
  function JsonSchemaViewer (line 8) | function JsonSchemaViewer({ path }: { path: string }) {
  function schemaPathFromPath (line 40) | function schemaPathFromPath(path: JSONHeroPath | string): JSONHeroPath {

FILE: app/components/JsonTreeView.tsx
  function JsonTreeView (line 14) | function JsonTreeView() {
  function TreeViewNode (line 116) | function TreeViewNode({
  function computeTreeNodePaddingClass (line 223) | function computeTreeNodePaddingClass(depth: number) {

FILE: app/components/JsonView.tsx
  function JsonView (line 5) | function JsonView({ children }: { children: React.ReactNode }) {

FILE: app/components/NewDocument.tsx
  function NewDocument (line 6) | function NewDocument() {

FILE: app/components/NewFile.tsx
  function NewFile (line 6) | function NewFile() {

FILE: app/components/OpenInWindow.tsx
  type OpenInNewWindowProps (line 1) | type OpenInNewWindowProps = {
  function OpenInNewWindow (line 7) | function OpenInNewWindow({

FILE: app/components/PathBar.tsx
  function PathBar (line 19) | function PathBar() {
  function PathBarText (line 50) | function PathBarText({ selectedNodes, onConfirm }: { selectedNodes: Colu...
  type PathBarLinkProps (line 95) | type PathBarLinkProps = {
  function PathBarLink (line 101) | function PathBarLink({
  function PathHistoryControls (line 137) | function PathHistoryControls() {
  function PathBarElement (line 177) | function PathBarElement({

FILE: app/components/PathPreview.tsx
  type PathPreviewProps (line 9) | type PathPreviewProps = {
  type ValueComponent (line 15) | type ValueComponent = {
  type EllipsisComponent (line 22) | type EllipsisComponent = {
  type Component (line 27) | type Component = ValueComponent | EllipsisComponent;
  function PathPreview (line 29) | function PathPreview({

FILE: app/components/PreferencesProvider.tsx
  type Preferences (line 3) | interface Preferences {
  type PreferencesContextType (line 11) | type PreferencesContextType = [
  function PreferencesProvider (line 28) | function PreferencesProvider({
  function usePreferences (line 52) | function usePreferences() {

FILE: app/components/Preview/CalendarMonth.tsx
  type CalendarMonthProps (line 4) | type CalendarMonthProps = {
  type Day (line 8) | type Day = {
  function dateString (line 14) | function dateString(date: Date): string {
  function isSameDay (line 18) | function isSameDay(date: Date, otherDate: Date): boolean {
  function CalendarMonth (line 26) | function CalendarMonth({ date }: CalendarMonthProps) {
  function classNames (line 136) | function classNames(...classes: (string | boolean)[]) {

FILE: app/components/Preview/PreviewBox.tsx
  type PreviewBoxProps (line 4) | type PreviewBoxProps = {
  function PreviewBox (line 10) | function PreviewBox({ link, className, children }: PreviewBoxProps) {

FILE: app/components/Preview/PreviewProperties.tsx
  type PreviewPropertyProps (line 3) | type PreviewPropertyProps = {
  type PreviewProperty (line 7) | type PreviewProperty = {
  function PreviewProperties (line 13) | function PreviewProperties({ properties }: PreviewPropertyProps) {

FILE: app/components/Preview/PreviewValue.tsx
  function PreviewValue (line 4) | function PreviewValue() {

FILE: app/components/Preview/Types/PreviewAudioUri.tsx
  function PreviewAudioUri (line 6) | function PreviewAudioUri({

FILE: app/components/Preview/Types/PreviewDate.tsx
  type PreviewDateProps (line 7) | type PreviewDateProps = {
  function PreviewDate (line 12) | function PreviewDate({ value, format }: PreviewDateProps) {

FILE: app/components/Preview/Types/PreviewHtml.tsx
  type PreviewHtmlProps (line 6) | type PreviewHtmlProps = {
  function PreviewHtml (line 10) | function PreviewHtml({ info }: PreviewHtmlProps) {

FILE: app/components/Preview/Types/PreviewIPFSImage.tsx
  function PreviewIPFSImage (line 3) | function PreviewIPFSImage({ src }: { src: URL }) {

FILE: app/components/Preview/Types/PreviewImage.tsx
  type PreviewImageProps (line 6) | type PreviewImageProps = {
  function PreviewImage (line 10) | function PreviewImage({ info }: PreviewImageProps) {

FILE: app/components/Preview/Types/PreviewImageUri.tsx
  function PreviewImageUri (line 4) | function PreviewImageUri({

FILE: app/components/Preview/Types/PreviewJson.tsx
  function PreviewJson (line 9) | function PreviewJson({ preview }: { preview: PreviewJson }) {

FILE: app/components/Preview/Types/PreviewString.tsx
  function PreviewString (line 16) | function PreviewString({ info }: { info: JSONStringType }) {
  function PreviewJson (line 81) | function PreviewJson({
  function PreviewColor (line 95) | function PreviewColor({

FILE: app/components/Preview/Types/PreviewUri.tsx
  type PreviewUriProps (line 10) | type PreviewUriProps = {
  function PreviewUri (line 15) | function PreviewUri(props: PreviewUriProps) {

FILE: app/components/Preview/Types/PreviewUriElement.tsx
  type PreviewUriElementProps (line 6) | type PreviewUriElementProps = {
  function PreviewUriElement (line 10) | function PreviewUriElement({ info }: PreviewUriElementProps) {

FILE: app/components/Preview/Types/PreviewVideoUri.tsx
  function PreviewVideoUri (line 6) | function PreviewVideoUri({

FILE: app/components/Preview/Types/RetweetIcon.tsx
  function RetweetIcon (line 1) | function RetweetIcon(props: React.SVGProps<SVGSVGElement>) {

FILE: app/components/Preview/Types/preview.types.d.ts
  type PreviewImage (line 1) | type PreviewImage = {
  type PreviewVideo (line 9) | type PreviewVideo = {
  type PreviewHtml (line 17) | type PreviewHtml = {
  type PreviewJson (line 29) | type PreviewJson = {
  type PreviewInfo (line 35) | type PreviewInfo = PreviewImage | PreviewHtml | PreviewJson;
  type PreviewResult (line 36) | type PreviewResult = PreviewInfo | { error: string };
  type YouTubeLinkDetails (line 38) | type YouTubeLinkDetails = {
  type TwitterLinkDetails (line 49) | type TwitterLinkDetails = {
  type ImageAssetDetails (line 57) | type ImageAssetDetails = {
  type OpenGraphMedia (line 66) | type OpenGraphMedia = {
  type OpenGraphTwitterImage (line 73) | type OpenGraphTwitterImage = {
  type OpenGraphTwitterPlayer (line 80) | type OpenGraphTwitterPlayer = {
  type OpenGraphMusicSong (line 87) | type OpenGraphMusicSong = {
  type OpenGraphDetails (line 93) | type OpenGraphDetails = {
  type OpenGraphPreviewData (line 264) | type OpenGraphPreviewData = {
  type OpenGraphPreviewDataError (line 276) | type OpenGraphPreviewDataError = {

FILE: app/components/Properties/PropertiesFloat.tsx
  type PropertiesFloatProps (line 6) | type PropertiesFloatProps = {
  function PropertiesFloat (line 10) | function PropertiesFloat(info: PropertiesFloatProps) {

FILE: app/components/Properties/PropertiesInt.tsx
  type PropertiesNumberProps (line 9) | type PropertiesNumberProps = {
  function PropertiesInt (line 13) | function PropertiesInt({ type }: { type: JSONIntType }) {
  function PropertiesTimestamp (line 39) | function PropertiesTimestamp({

FILE: app/components/Properties/PropertiesString.tsx
  type PropertiesStringProps (line 11) | type PropertiesStringProps = {
  function PropertiesString (line 15) | function PropertiesString({ type }: { type: JSONStringType }) {
  function PropertiesJwt (line 39) | function PropertiesJwt({
  function PropertiesTimestamp (line 72) | function PropertiesTimestamp({
  function PropertiesDateTime (line 116) | function PropertiesDateTime({
  function PropertiesColor (line 182) | function PropertiesColor({
  function PropertiesUri (line 217) | function PropertiesUri({

FILE: app/components/Properties/PropertiesValue.tsx
  function PropertiesValue (line 6) | function PropertiesValue() {

FILE: app/components/RelatedValues.tsx
  type RelatedValuesProps (line 15) | type RelatedValuesProps = {
  function RelatedValues (line 19) | function RelatedValues({ relatedPaths }: RelatedValuesProps) {
  function RelatedValuesGroupItem (line 62) | function RelatedValuesGroupItem({
  function PathLink (line 115) | function PathLink({ path, enabled }: { path: string; enabled: boolean }) {

FILE: app/components/Resizable.tsx
  type ResizableProps (line 3) | type ResizableProps = {
  function Resizable (line 11) | function Resizable({

FILE: app/components/SampleUrls.tsx
  function SampleUrls (line 3) | function SampleUrls() {

FILE: app/components/SearchBar.tsx
  function SearchBar (line 13) | function SearchBar() {

FILE: app/components/SearchPalette.tsx
  function SearchPalette (line 30) | function SearchPalette({
  type SearchItemProps (line 212) | type SearchItemProps = {
  function SearchItem (line 218) | function SearchItem({
  function SearchPathResult (line 289) | function SearchPathResult({
  function SearchResultValue (line 380) | function SearchResultValue({
  function createOutputForMatch (line 415) | function createOutputForMatch(

FILE: app/components/Share.tsx
  function Share (line 13) | function Share() {

FILE: app/components/SideBar.tsx
  function SideBar (line 11) | function SideBar() {
  function SidebarLink (line 68) | function SidebarLink({

FILE: app/components/StarCountProvider.tsx
  type StarCountType (line 4) | type StarCountType = number | undefined;
  function StarCountProvider (line 8) | function StarCountProvider({
  function useStarCount (line 22) | function useStarCount(): StarCountType {

FILE: app/components/ThemeModeToggle.tsx
  function ThemeModeToggler (line 6) | function ThemeModeToggler() {

FILE: app/components/ThemeProvider.tsx
  type Theme (line 5) | type Theme = "dark" | "light";
  type ThemeContextType (line 7) | type ThemeContextType = [
  function ThemeProvider (line 18) | function ThemeProvider({
  function useTheme (line 71) | function useTheme(): ThemeContextType {
  function NonFlashOfWrongThemeEls (line 97) | function NonFlashOfWrongThemeEls({ ssrTheme }: { ssrTheme: boolean }) {
  function isTheme (line 107) | function isTheme(value: unknown): value is Theme {

FILE: app/components/ToolTip.tsx
  type ToolTipProps (line 4) | type ToolTipProps = {
  type ArrowDirection (line 10) | type ArrowDirection = "top" | "bottom" | "left" | "right";
  function ToolTip (line 12) | function ToolTip({ children, className, arrow }: ToolTipProps) {

FILE: app/components/UI/GithubStar.tsx
  type GithubStarProps (line 6) | type GithubStarProps = {
  function GithubStar (line 10) | function GithubStar({ className }: GithubStarProps) {

FILE: app/components/UI/GithubStarSmall.tsx
  type GithubStarSmallProps (line 6) | type GithubStarSmallProps = {
  function GithubStarSmall (line 10) | function GithubStarSmall({ className }: GithubStarSmallProps) {

FILE: app/components/UI/Tabs.tsx
  type TabProps (line 6) | type TabProps = {
  function Tabs (line 11) | function Tabs({ tabs, children }: TabProps) {

FILE: app/components/UrlForm.tsx
  type UrlFormProps (line 4) | type UrlFormProps = {
  function UrlForm (line 8) | function UrlForm({ className }: UrlFormProps) {

FILE: app/components/ValueIcon.tsx
  type ValueIconProps (line 30) | type ValueIconProps = {
  type ValueIconSize (line 36) | enum ValueIconSize {

FILE: app/components/json-schema-map.d.ts
  type ParseOptions (line 2) | interface ParseOptions {
  type PointerProp (line 6) | type PointerProp = "value" | "valueEnd" | "key" | "keyEnd";
  type Location (line 8) | interface Location {
  type Pointers (line 14) | type Pointers = Record<string, Record<PointerProp, Location>>;
  type ParseResult (line 16) | interface ParseResult {
  type StringifyOptions (line 27) | interface StringifyOptions {
  type StringifyResult (line 32) | interface StringifyResult {

FILE: app/entry.server.tsx
  function handleRequest (line 5) | function handleRequest(

FILE: app/entry.worker.ts
  type SearchWorker (line 6) | type SearchWorker = {
  type InitializeIndexEvent (line 13) | type InitializeIndexEvent = {
  type SearchEvent (line 18) | type SearchEvent = {
  type SearchWorkerEvent (line 23) | type SearchWorkerEvent = InitializeIndexEvent | SearchEvent;
  function valueFormatter (line 72) | function valueFormatter(value: unknown): string | undefined {

FILE: app/graphJSON.server.ts
  function sendEvent (line 1) | async function sendEvent(event: Record<string, any>): Promise<void> {
  function graphJsonReplacer (line 5) | function graphJsonReplacer(key: string, value: any): any {

FILE: app/hooks/useClickOutside.tsx
  function useClickOutside (line 3) | function useClickOutside(

FILE: app/hooks/useIsMounted.tsx
  function useIsMounted (line 3) | function useIsMounted(): () => boolean {

FILE: app/hooks/useJson.tsx
  type JsonContextType (line 13) | type JsonContextType = [unknown, Dispatch<SetStateAction<unknown>>];
  function JsonProvider (line 17) | function JsonProvider({
  function useJson (line 35) | function useJson(): JsonContextType {

FILE: app/hooks/useJsonColumnView.tsx
  type JsonColumnViewState (line 21) | type JsonColumnViewState = ColumnViewInstanceState;
  type JsonColumnViewAPI (line 22) | type JsonColumnViewAPI = ColumnViewAPI;
  function JsonColumnViewProvider (line 32) | function JsonColumnViewProvider({ children }: { children: ReactNode }) {
  function useJsonColumnViewState (line 175) | function useJsonColumnViewState(): JsonColumnViewState {
  function useJsonColumnViewAPI (line 186) | function useJsonColumnViewAPI(): JsonColumnViewAPI {
  function isAncestorOf (line 197) | function isAncestorOf(ancestor: string, descendant: string) {

FILE: app/hooks/useJsonDoc.tsx
  type JsonDocType (line 5) | type JsonDocType = {
  function JsonDocProvider (line 13) | function JsonDocProvider({
  function useJsonDoc (line 31) | function useJsonDoc(): JsonDocType {

FILE: app/hooks/useJsonSchema.tsx
  function JsonSchemaProvider (line 9) | function JsonSchemaProvider({ children }: { children: ReactNode }) {
  function useJsonSchema (line 24) | function useJsonSchema(): Schema {

FILE: app/hooks/useJsonSearch.tsx
  type InitializeIndexEvent (line 13) | type InitializeIndexEvent = {
  type SearchEvent (line 18) | type SearchEvent = {
  type SearchSendWorkerEvent (line 23) | type SearchSendWorkerEvent = InitializeIndexEvent | SearchEvent;
  type IndexInitializedEvent (line 25) | type IndexInitializedEvent = {
  type SearchResultsEvent (line 29) | type SearchResultsEvent = {
  type SearchReceiveWorkerEvent (line 34) | type SearchReceiveWorkerEvent =
  type JsonSearchApi (line 38) | type JsonSearchApi = {
  type JsonSearchState (line 49) | type JsonSearchState = {
  type SearchAction (line 55) | type SearchAction = {
  type ResetAction (line 60) | type ResetAction = {
  type JsonSearchAction (line 64) | type JsonSearchAction = SearchReceiveWorkerEvent | SearchAction | ResetA...
  function reducer (line 66) | function reducer(
  function wrapReducer (line 129) | function wrapReducer<S, A extends { type: string }>(
  function JsonSearchProvider (line 173) | function JsonSearchProvider({
  function useJsonSearchState (line 244) | function useJsonSearchState(): JsonSearchState {
  function useJsonSearchApi (line 248) | function useJsonSearchApi(): JsonSearchApi {

FILE: app/hooks/useJsonTree.tsx
  type JsonTreeOptions (line 21) | type JsonTreeOptions = {
  type UseJsonTreeInstance (line 25) | type UseJsonTreeInstance = {
  type JsonTreeViewType (line 30) | type JsonTreeViewType = UseJsonTreeInstance;
  function JsonTreeViewProvider (line 36) | function JsonTreeViewProvider({
  function useJsonTree (line 49) | function useJsonTree(options: JsonTreeOptions): UseJsonTreeInstance {
  function useJsonTreeViewContext (line 71) | function useJsonTreeViewContext(): JsonTreeViewType {
  type JsonTreeViewNode (line 82) | type JsonTreeViewNode = {
  function generateTreeViewNodes (line 92) | function generateTreeViewNodes(json: unknown): Array<JsonTreeViewNode> {
  function generateChildren (line 99) | function generateChildren(

FILE: app/hooks/useLoadWhenOnline.tsx
  function useLoadWhenOnline (line 3) | function useLoadWhenOnline(callback: () => void, deps: unknown[] = []) {

FILE: app/hooks/useMemoCompare.ts
  function useMemoCompare (line 3) | function useMemoCompare<T>(

FILE: app/hooks/useOnScreen.tsx
  function useOnScreen (line 3) | function useOnScreen(ref: RefObject<HTMLElement>) {

FILE: app/hooks/useRelatedPaths.ts
  function useRelatedPaths (line 6) | function useRelatedPaths(): string[] {

FILE: app/hooks/useSelectedInfo.tsx
  function useSelectedInfo (line 7) | function useSelectedInfo(): JSONValueType | undefined {

FILE: app/hooks/useVirtualTree.ts
  type UseVirtualOptions (line 12) | type UseVirtualOptions<R> = Parameters<typeof useVirtual>[0];
  type UseVirtualTreeOptions (line 14) | type UseVirtualTreeOptions<
  type VirtualNode (line 23) | type VirtualNode<T> = {
  type UseVirtualTreeInstance (line 33) | type UseVirtualTreeInstance<T> = {
  type TreeNodeItem (line 45) | type TreeNodeItem<T extends { id: string; children?: T[] }> = {
  type TreeState (line 54) | type TreeState<T extends { id: string; children?: T[] }> = {
  type ToggleNodeAction (line 61) | type ToggleNodeAction = {
  type FocusNodeAction (line 67) | type FocusNodeAction = {
  type MoveNodeAction (line 72) | type MoveNodeAction = {
  type MoveRightAction (line 77) | type MoveRightAction = {
  type MoveLeftAction (line 82) | type MoveLeftAction = {
  type FocusFirstAction (line 87) | type FocusFirstAction = {
  type RestoreStateAction (line 91) | type RestoreStateAction = {
  type ExpandAllOnPathAction (line 96) | type ExpandAllOnPathAction = {
  type BlurAction (line 101) | type BlurAction = {
  type CollapseAllNodesAction (line 105) | type CollapseAllNodesAction = {
  type TreeAction (line 109) | type TreeAction =
  function expandNode (line 121) | function expandNode<T extends { id: string; children?: T[] }>(
  function collapseNode (line 138) | function collapseNode<T extends { id: string; children?: T[] }>(
  function toggleAllChildren (line 154) | function toggleAllChildren<T extends { id: string; children?: T[] }>(
  function useVirtualTree (line 205) | function useVirtualTree<T extends { id: string; children?: T[] }, R>(
  function createNodeItems (line 608) | function createNodeItems<T extends { id: string; children?: T[] }>(
  function createTreeProps (line 633) | function createTreeProps<T extends { id: string; children?: T[] }>(
  function createItemProps (line 696) | function createItemProps<T extends { id: string; children?: T[] }>(
  function findNodeInTreeById (line 724) | function findNodeInTreeById<T extends { id: string; children?: T[] }>(
  function calculatePathToNode (line 745) | function calculatePathToNode<T extends { id: string; children?: T[] }>(

FILE: app/jsonDoc.server.ts
  type BaseJsonDocument (line 6) | type BaseJsonDocument = {
  type RawJsonDocument (line 12) | type RawJsonDocument = BaseJsonDocument & {
  type UrlJsonDocument (line 17) | type UrlJsonDocument = BaseJsonDocument & {
  type CreateJsonOptions (line 22) | type CreateJsonOptions = {
  type JSONDocument (line 29) | type JSONDocument = RawJsonDocument | UrlJsonDocument;
  function createFromUrlOrRawJson (line 31) | async function createFromUrlOrRawJson(
  function createFromUrl (line 50) | async function createFromUrl(
  function createFromRawJson (line 83) | async function createFromRawJson(
  function getDocument (line 106) | async function getDocument(
  function updateDocument (line 116) | async function updateDocument(
  function deleteDocument (line 131) | async function deleteDocument(slug: string): Promise<void> {
  function createId (line 135) | function createId(): string {
  function isUrl (line 148) | function isUrl(possibleUrl: string): boolean {
  function isJSON (line 157) | function isJSON(possibleJson: string): boolean {

FILE: app/root.tsx
  function links (line 49) | function links() {
  type LoaderData (line 53) | type LoaderData = {
  function getThemeFromRequest (line 73) | function getThemeFromRequest(request: Request): Theme | undefined {
  function App (line 82) | function App() {
  function AppWithProviders (line 103) | function AppWithProviders() {

FILE: app/routes/actions/createFromFile.ts
  type CreateFromFileError (line 6) | type CreateFromFileError = {

FILE: app/routes/actions/createFromUrl.ts
  type CreateFromUrlError (line 12) | type CreateFromUrlError = {

FILE: app/routes/actions/getPreview.$url.ts
  function earlyRespondIfHomepagePreviewUri (line 43) | function earlyRespondIfHomepagePreviewUri(uri: string) {

FILE: app/routes/index.tsx
  type LoaderData (line 18) | type LoaderData = { toastMessage?: ToastMessage };
  function loader (line 20) | async function loader({ request }: { request: Request }) {
  function Index (line 32) | function Index() {

FILE: app/routes/j/$id.tsx
  function getPathFromRequest (line 125) | function getPathFromRequest(request: Request): string | null {
  function getMinimalFromRequest (line 141) | function getMinimalFromRequest(request: Request): boolean | undefined {
  type LoaderData (line 153) | type LoaderData = {
  function JsonDocumentRoute (line 178) | function JsonDocumentRoute() {
  function CatchBoundary (line 250) | function CatchBoundary() {

FILE: app/routes/j/$id/editor.tsx
  function EditorView (line 3) | function EditorView() {

FILE: app/routes/j/$id/index.tsx
  function DefaultView (line 3) | function DefaultView() {

FILE: app/routes/j/$id/terminal.tsx
  function TerminalViewPage (line 5) | function TerminalViewPage() {

FILE: app/routes/j/$id/tree.tsx
  function TreeViewPage (line 3) | function TreeViewPage() {

FILE: app/services/github.server.ts
  function getStarCount (line 1) | async function getStarCount(): Promise<number | undefined> {

FILE: app/services/toast.server.ts
  type ToastMessage (line 3) | type ToastMessage = {
  constant ONE_YEAR (line 10) | const ONE_YEAR = 1000 * 60 * 60 * 24 * 365;
  function setSuccessMessage (line 24) | function setSuccessMessage(
  function setErrorMessage (line 37) | function setErrorMessage(

FILE: app/services/uriPreview.server.ts
  function getOpenGraphNinja (line 19) | async function getOpenGraphNinja(link: string): Promise<PreviewResult> {
  function getUriPreview (line 46) | async function getUriPreview(uri: string): Promise<PreviewResult> {
  type HeadInfo (line 83) | type HeadInfo = {
  function headUri (line 89) | async function headUri(
  function createPreviewJson (line 136) | function createPreviewJson(uri: string, json: unknown): PreviewJson {
  function createPreviewImage (line 144) | function createPreviewImage(uri: string, head: HeadInfo): PreviewImage {
  function rewriteUrl (line 155) | function rewriteUrl(url: string): URL {

FILE: app/theme.server.ts
  function getThemeSession (line 18) | async function getThemeSession(request: Request) {

FILE: app/useColumnView/index.ts
  type IconComponent (line 12) | type IconComponent = (
  type ColumnViewNode (line 16) | interface ColumnViewNode {
  type ColumnViewOptions (line 26) | type ColumnViewOptions = {
  type ColumnDefinition (line 32) | type ColumnDefinition = {
  type ColumnViewInstanceState (line 39) | type ColumnViewInstanceState = {
  type ColumnViewAPIOptions (line 52) | type ColumnViewAPIOptions = {
  type ColumnViewAPI (line 56) | type ColumnViewAPI = {
  type ColumnViewInstance (line 67) | type ColumnViewInstance = {
  type ColumnViewProps (line 72) | type ColumnViewProps = {
  type ColumnDefinitionCache (line 78) | type ColumnDefinitionCache = Map<string, ColumnDefinition>;
  function useColumnView (line 80) | function useColumnView({
  type ColumnViewState (line 232) | type ColumnViewState = {
  type SetSelectedNodeIdAction (line 242) | type SetSelectedNodeIdAction = {
  type MoveSelectedNodeAction (line 248) | type MoveSelectedNodeAction = {
  type ResetSelectionNodeAction (line 253) | type ResetSelectionNodeAction = {
  type GoAction (line 257) | type GoAction = {
  type ColumnViewAction (line 262) | type ColumnViewAction =
  function goBackAction (line 268) | function goBackAction(): GoAction {
  function goForwardAction (line 275) | function goForwardAction(): GoAction {
  function resetSelectionAction (line 282) | function resetSelectionAction(): ResetSelectionNodeAction {
  function goToNodeIdAction (line 288) | function goToNodeIdAction(
  function goToParentAction (line 299) | function goToParentAction(
  function goToChildrenAction (line 308) | function goToChildrenAction(): MoveSelectedNodeAction {
  function goToPreviousSibling (line 314) | function goToPreviousSibling(): MoveSelectedNodeAction {
  function goToNextSibling (line 320) | function goToNextSibling(): MoveSelectedNodeAction {
  type ColumnViewStateReducerHook (line 326) | type ColumnViewStateReducerHook = (
  function columnViewReducer (line 345) | function columnViewReducer(
  function moveToChildren (line 455) | function moveToChildren(state: ColumnViewState): ColumnViewState {
  function generateColumns (line 478) | function generateColumns(
  function getPathToNode (line 516) | function getPathToNode(nodeTable: NodeTable, nodeId?: string): string[] {
  function getNodeAncestorPath (line 520) | function getNodeAncestorPath(
  type NodeRecord (line 542) | type NodeRecord = {
  type NodeTable (line 549) | type NodeTable = {
  function generateNodeTable (line 553) | function generateNodeTable(rootNode: ColumnViewNode): NodeTable {
  function getHighlightedSibling (line 580) | function getHighlightedSibling(

FILE: app/utilities/classnames.ts
  function classnames (line 1) | function classnames(...args: string[]): string {

FILE: app/utilities/codeMirrorSetup.ts
  function getPreviewSetup (line 14) | function getPreviewSetup(): Array<Extension> {
  function getViewerSetup (line 26) | function getViewerSetup(): Array<Extension> {
  function getEditorSetup (line 30) | function getEditorSetup(): Array<Extension> {

FILE: app/utilities/codeMirrorTheme.ts
  function darkTheme (line 5) | function darkTheme(): Extension {
  function lightTheme (line 147) | function lightTheme(): Extension {

FILE: app/utilities/colors.ts
  function colorForTypeName (line 4) | function colorForTypeName(typeName: string): string {
  function colorForItemAtPath (line 31) | function colorForItemAtPath(path: string, json: unknown): string {

FILE: app/utilities/dataType.ts
  type HierarchicalTypes (line 3) | interface HierarchicalTypes {
  function concatenated (line 7) | function concatenated(types: HierarchicalTypes): string {
  function getHierarchicalTypes (line 11) | function getHierarchicalTypes(type: JSONValueType): HierarchicalTypes {

FILE: app/utilities/formatStarCount.ts
  function formatStarCount (line 4) | function formatStarCount(count: number | undefined): string {
  function roundWithPrecision (line 16) | function roundWithPrecision(value: number, precision: number): number {

FILE: app/utilities/formatter.ts
  function formatRawValue (line 10) | function formatRawValue(type: JSONValueType): string {
  type FormatValueOptions (line 29) | type FormatValueOptions = {
  function formatValue (line 33) | function formatValue(
  function formatNumber (line 85) | function formatNumber(value: number): string {
  function formatString (line 89) | function formatString(value: string, format?: JSONStringFormat): string {
  function formatDateTime (line 106) | function formatDateTime(
  function formatBytes (line 146) | function formatBytes(bytes: number, decimals = 2): string {

FILE: app/utilities/getRandomUserAgent.ts
  function getRandomUserAgent (line 104) | function getRandomUserAgent() {

FILE: app/utilities/icons.ts
  function iconForValue (line 31) | function iconForValue(value: unknown): IconComponent {
  function iconForType (line 35) | function iconForType(type: JSONValueType): IconComponent {

FILE: app/utilities/inferredTemporal.ts
  type InferredTemporal (line 4) | type InferredTemporal =
  function inferTemporal (line 11) | function inferTemporal(

FILE: app/utilities/jsonColumnView.ts
  function generateColumnViewNode (line 7) | function generateColumnViewNode(json: unknown): ColumnViewNode {
  function generateChildren (line 21) | function generateChildren(
  function generateNodesToPath (line 64) | function generateNodesToPath(
  function firstChildToDescendant (line 95) | function firstChildToDescendant(
  function pathToDescendant (line 119) | function pathToDescendant(
  function calculateStablePath (line 148) | function calculateStablePath(

FILE: app/utilities/nullable.ts
  function isNullable (line 3) | function isNullable(relatedPaths: string[], json: unknown): boolean {

FILE: app/utilities/relatedValues.ts
  type RelatedValuesGroup (line 5) | type RelatedValuesGroup = {
  function calculateRelatedValuesGroups (line 10) | function calculateRelatedValuesGroups(
  function groupRelatedValues (line 18) | function groupRelatedValues(
  function getRelatedPathsAtPath (line 52) | function getRelatedPathsAtPath(

FILE: app/utilities/safeFetch.ts
  function safeFetch (line 1) | function safeFetch(

FILE: app/utilities/search.ts
  type StringSlice (line 4) | type StringSlice = {
  function getStringSlices (line 23) | function getStringSlices(
  type EllispisSlice (line 104) | type EllispisSlice = {
  type ComponentSlice (line 108) | type ComponentSlice = {
  type JoinSlice (line 114) | type JoinSlice = {
  type PathSlice (line 118) | type PathSlice = EllispisSlice | ComponentSlice | JoinSlice;
  function getComponentSlices (line 187) | function getComponentSlices(
  function createComponentSlices (line 424) | function createComponentSlices(

FILE: app/utilities/stableJson.ts
  function stableJson (line 1) | function stableJson(json: unknown, keyOrder: string[] = []): unknown {

FILE: app/utilities/xml/convertFromRawXml.ts
  type SerializedXMLObject (line 3) | type SerializedXMLObject = {
  function convertFromRawXml (line 86) | function convertFromRawXml(xmlString: string): string {

FILE: app/utilities/xml/createFromRawXml.ts
  function createFromRawXml (line 8) | async function createFromRawXml(

FILE: app/utilities/xml/isXML.ts
  function isXML (line 3) | function isXML(possibleXml: string): boolean {

FILE: worker/index.js
  method getLoadContext (line 9) | getLoadContext(event) {
Condensed preview — 215 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (570K chars).
[
  {
    "path": ".github/workflows/main.yml",
    "chars": 873,
    "preview": "name: CI\n\non:\n  push:\n    branches: [main]\n    paths:\n      - \".github/workflows/main.yml\"\n      - \"app/**/*.ts\"\n      -"
  },
  {
    "path": ".gitignore",
    "chars": 141,
    "preview": "node_modules\n\n/.cache\n/build\n/public/build\n.env\n/app/tailwind.css\n/jsonDocs\n.DS_Store\n/dist\n.mf\n/meta.json\n/stats.html\np"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 1132,
    "preview": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  //"
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 251,
    "preview": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"type\": \"typescript\",\n      \"tsconfig\": \"tsconfig.json\",\n      \"option\""
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2340,
    "preview": "## ⚡️ JSON Hero Contributing Guide\n\nFirst of all, thanks for considering contributing to this project! If you have any q"
  },
  {
    "path": "DEVELOPMENT.md",
    "chars": 2753,
    "preview": "## 👩🏽‍💻 JSON Hero Local Development Guide\n\nWelcome to JSON Hero development and thanks for being here! If you'd like to "
  },
  {
    "path": "Dockerfile",
    "chars": 172,
    "preview": "# Builder\nFROM node:16.17.0 as builder\nWORKDIR /src\nCOPY . /src\n\n# App\nRUN cd /src\nRUN npm install\nRUN echo \"SESSION_SEC"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 7310,
    "preview": "<div align=\"center\">\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://imagedelivery.net/3Tbraffu"
  },
  {
    "path": "SELF_HOSTING.md",
    "chars": 1189,
    "preview": "## Deploying to Cloudflare\n\n### Install and login to wrangler\n```bash\nnpm install -g wrangler\nwrangler login\n```\n\n### Cr"
  },
  {
    "path": "app/bindings.d.ts",
    "chars": 207,
    "preview": "export {};\n\ndeclare global {\n  const DOCUMENTS: KVNamespace;\n  const SESSION_SECRET: string;\n  const GRAPH_JSON_API_KEY:"
  },
  {
    "path": "app/components/AutoplayVideo.tsx",
    "chars": 663,
    "preview": "import { useEffect, useRef } from \"react\";\nimport { useOnScreen } from \"~/hooks/useOnScreen\";\n\nexport function AutoplayV"
  },
  {
    "path": "app/components/BlankColumn.tsx",
    "chars": 279,
    "preview": "import { memo } from \"react\";\n\nfunction BlankColumnElement() {\n  return (\n    <div\n      className={\n        \"column fle"
  },
  {
    "path": "app/components/CodeEditor.tsx",
    "chars": 2966,
    "preview": "import { json as jsonLang } from \"@codemirror/lang-json\";\nimport {\n  EditorView,\n  TransactionSpec,\n  useCodeMirror,\n  V"
  },
  {
    "path": "app/components/CodeViewer.tsx",
    "chars": 1285,
    "preview": "import { json as jsonLang } from \"@codemirror/lang-json\";\nimport { useCodeMirror } from \"@uiw/react-codemirror\";\nimport "
  },
  {
    "path": "app/components/Column.tsx",
    "chars": 1340,
    "preview": "import { Title } from \"./Primitives/Title\";\nimport { colorForItemAtPath } from \"~/utilities/colors\";\nimport { IconCompon"
  },
  {
    "path": "app/components/ColumnItem.tsx",
    "chars": 2854,
    "preview": "import { ChevronRightIcon } from \"@heroicons/react/outline\";\nimport { Mono } from \"./Primitives/Mono\";\nimport { memo, us"
  },
  {
    "path": "app/components/Columns.tsx",
    "chars": 1940,
    "preview": "import { JSONHeroPath } from \"@jsonhero/path\";\nimport { memo, useMemo } from \"react\";\nimport { useJson } from \"~/hooks/u"
  },
  {
    "path": "app/components/ContainerInfo.tsx",
    "chars": 2221,
    "preview": "import { inferType } from \"@jsonhero/json-infer-types\";\nimport { JSONHeroPath } from \"@jsonhero/path\";\nimport { useJson "
  },
  {
    "path": "app/components/CopySelectedNode.tsx",
    "chars": 520,
    "preview": "import { useHotkeys } from \"react-hotkeys-hook\";\nimport { useSelectedInfo } from \"../hooks/useSelectedInfo\";\n\nexport fun"
  },
  {
    "path": "app/components/CopyText.tsx",
    "chars": 507,
    "preview": "import React, { useCallback } from \"react\";\n\nexport type CopyTextProps = {\n  children?: React.ReactNode;\n  value: string"
  },
  {
    "path": "app/components/CopyTextButton.tsx",
    "chars": 863,
    "preview": "import { ClipboardIcon } from \"@heroicons/react/outline\";\nimport { useCallback, useState } from \"react\";\nimport { CopyTe"
  },
  {
    "path": "app/components/DataTable.tsx",
    "chars": 2278,
    "preview": "import { FunctionComponent, useState } from \"react\";\nimport { CopyTextButton } from \"./CopyTextButton\";\nimport { Title }"
  },
  {
    "path": "app/components/DocumentTitle.tsx",
    "chars": 3107,
    "preview": "import { PencilAltIcon } from \"@heroicons/react/outline\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { "
  },
  {
    "path": "app/components/DragAndDropForm.tsx",
    "chars": 2855,
    "preview": "import { ArrowCircleDownIcon } from \"@heroicons/react/outline\";\nimport { useCallback, useRef } from \"react\";\nimport { us"
  },
  {
    "path": "app/components/ExampleDoc.tsx",
    "chars": 368,
    "preview": "import { Link } from \"remix\";\n\nexport function ExampleDoc({\n  id,\n  title,\n  path,\n}: {\n  id: string;\n  title: string;\n "
  },
  {
    "path": "app/components/ExampleUrl.tsx",
    "chars": 630,
    "preview": "import { Form } from \"remix\";\n\nexport function ExampleUrl({\n  url,\n  title,\n  displayTitle,\n}: {\n  url: string;\n  title:"
  },
  {
    "path": "app/components/FileSelector/FileDropzone.tsx",
    "chars": 1700,
    "preview": "import { FunctionComponent, useCallback } from \"react\";\nimport { useDropzone } from \"react-dropzone\";\nimport { DocumentD"
  },
  {
    "path": "app/components/Footer.tsx",
    "chars": 2634,
    "preview": "import { useJsonDoc } from \"~/hooks/useJsonDoc\";\nimport { ArrowKeysIcon } from \"./Icons/ArrowKeysIcon\";\nimport { CopySho"
  },
  {
    "path": "app/components/Header.tsx",
    "chars": 3676,
    "preview": "import { ShareIcon, PlusIcon, TrashIcon } from \"@heroicons/react/outline\";\nimport { DocumentTitle } from \"./DocumentTitl"
  },
  {
    "path": "app/components/Home/HomeApiHeroBanner.tsx",
    "chars": 1425,
    "preview": "import { Body } from \"../Primitives/Body\";\nimport { HomeApiHeroLaptop } from \"./HomeApiHeroLaptop\";\n\nexport function Hom"
  },
  {
    "path": "app/components/Home/HomeApiHeroLaptop.tsx",
    "chars": 241,
    "preview": "import ApiHeroLaptop from \"~/assets/images/apihero-laptop.png\";\n\nexport type IconProps = {\n  className?: string;\n};\n\nexp"
  },
  {
    "path": "app/components/Home/HomeCollaborateSection.tsx",
    "chars": 1070,
    "preview": "import { AutoplayVideo } from \"../AutoplayVideo\";\nimport { ExtraLargeTitle } from \"../Primitives/ExtraLargeTitle\";\nimpor"
  },
  {
    "path": "app/components/Home/HomeEdgeCasesSection.tsx",
    "chars": 1096,
    "preview": "import { AutoplayVideo } from \"../AutoplayVideo\";\nimport { ExtraLargeTitle } from \"../Primitives/ExtraLargeTitle\";\nimpor"
  },
  {
    "path": "app/components/Home/HomeFeatureGridSection.tsx",
    "chars": 3037,
    "preview": "import {\n  FastForwardIcon,\n  MoonIcon,\n  ClockIcon,\n  CodeIcon,\n  LockOpenIcon,\n  CubeTransparentIcon,\n} from \"@heroico"
  },
  {
    "path": "app/components/Home/HomeFooter.tsx",
    "chars": 1753,
    "preview": "import { Link } from \"remix\";\nimport { DiscordIcon } from \"../Icons/DiscordIcon\";\nimport { EmailIcon } from \"../Icons/Em"
  },
  {
    "path": "app/components/Home/HomeGithubBanner.tsx",
    "chars": 389,
    "preview": "import { Body } from \"../Primitives/Body\";\nimport { GithubStar } from \"../UI/GithubStar\";\n\nexport function GithubBanner("
  },
  {
    "path": "app/components/Home/HomeGridFeatureItem.tsx",
    "chars": 689,
    "preview": "import { IconComponent } from \"~/useColumnView\";\nimport { Body } from \"../Primitives/Body\";\nimport { Title } from \"../Pr"
  },
  {
    "path": "app/components/Home/HomeHeader.tsx",
    "chars": 2710,
    "preview": "import { DiscordIconTransparent } from \"../Icons/DiscordIconTransparent\";\nimport { EmailIconTransparent } from \"../Icons"
  },
  {
    "path": "app/components/Home/HomeHeroSection.tsx",
    "chars": 1449,
    "preview": "import { AutoplayVideo } from \"../AutoplayVideo\";\nimport { NewFile } from \"../NewFile\";\nimport { ExtraLargeTitle } from "
  },
  {
    "path": "app/components/Home/HomeInfoBoxSection.tsx",
    "chars": 4619,
    "preview": "import React, { useEffect, useRef, useState } from \"react\";\nimport { JsonProvider } from \"~/hooks/useJson\";\nimport { Jso"
  },
  {
    "path": "app/components/Home/HomeSearchSection.tsx",
    "chars": 987,
    "preview": "import { AutoplayVideo } from \"../AutoplayVideo\";\nimport { ExtraLargeTitle } from \"../Primitives/ExtraLargeTitle\";\nimpor"
  },
  {
    "path": "app/components/Home/HomeSection.tsx",
    "chars": 660,
    "preview": "export type HomeSectionProps = {\n  containerClassName?: string;\n  maxWidth?: string;\n  reversed?: boolean;\n  flipped?: b"
  },
  {
    "path": "app/components/Home/HomeSplitSection.tsx",
    "chars": 811,
    "preview": "import React from \"react\";\n\nexport type HomeSplitSectionProps = {\n  className?: string;\n  children: React.ReactNode;\n};\n"
  },
  {
    "path": "app/components/Icons/ArrayIcon.tsx",
    "chars": 943,
    "preview": "export function ArrayIcon(props: React.SVGProps<SVGSVGElement>) {\n\n  return (\n    <svg\n    className={props.className}\n "
  },
  {
    "path": "app/components/Icons/ArrowKeysIcon.tsx",
    "chars": 1524,
    "preview": "export function ArrowKeysIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.classNa"
  },
  {
    "path": "app/components/Icons/ArrowKeysUpDownIcon.tsx",
    "chars": 898,
    "preview": "export function ArrowKeysUpDownIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.c"
  },
  {
    "path": "app/components/Icons/CopyShortcutIcon.tsx",
    "chars": 1518,
    "preview": "export function CopyShortcutIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.clas"
  },
  {
    "path": "app/components/Icons/DiscordIcon.tsx",
    "chars": 5037,
    "preview": "export function DiscordIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.className"
  },
  {
    "path": "app/components/Icons/DiscordIconTransparent.tsx",
    "chars": 5708,
    "preview": "export function DiscordIconTransparent(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={prop"
  },
  {
    "path": "app/components/Icons/EmailIcon.tsx",
    "chars": 1505,
    "preview": "export function EmailIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    \n     <svg width=\"24\" \n     height=\"24\""
  },
  {
    "path": "app/components/Icons/EmailIconTransparent.tsx",
    "chars": 1473,
    "preview": "export function EmailIconTransparent(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    \n      <svg width=\"24\" \n    "
  },
  {
    "path": "app/components/Icons/EscapeKeyIcon.tsx",
    "chars": 3452,
    "preview": "export function EscapeKeyIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.classNa"
  },
  {
    "path": "app/components/Icons/GithubIcon.tsx",
    "chars": 4012,
    "preview": "export function GithubIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.className}"
  },
  {
    "path": "app/components/Icons/GithubIconSimple.tsx",
    "chars": 1765,
    "preview": "export type IconProps = {\n  className?: string;\n};\n\nexport function GithubIconSimple({ className }: IconProps) {\n  retur"
  },
  {
    "path": "app/components/Icons/LoadingIcon.tsx",
    "chars": 556,
    "preview": "export function LoadingIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.className"
  },
  {
    "path": "app/components/Icons/Logo.tsx",
    "chars": 5885,
    "preview": "import { Link } from \"remix\";\n\nexport function Logo({\n  className,\n  width = \"100%\",\n}: {\n  className?: string;\n  width?"
  },
  {
    "path": "app/components/Icons/LogoTriggerdotdev.tsx",
    "chars": 5087,
    "preview": "export function LogoTriggerdotdev({\n  className,\n  width = \"100%\",\n}: {\n  className?: string;\n  width?: string;\n}) {\n  r"
  },
  {
    "path": "app/components/Icons/MoonIcon.tsx",
    "chars": 1093,
    "preview": "import { motion } from \"framer-motion\";\nimport { transition } from \"../../utilities/animationConstants\";\n\nexport const M"
  },
  {
    "path": "app/components/Icons/ObjectIcon.tsx",
    "chars": 3279,
    "preview": "export function ObjectIcon(props: React.SVGProps<SVGSVGElement>) {\n\n  return (\n    <svg\n    className={props.className}\n"
  },
  {
    "path": "app/components/Icons/ShortcutIcon.tsx",
    "chars": 307,
    "preview": "export type ShortcutIconProps = {\n  children: React.ReactNode;\n  className?: string;\n};\n\nexport function ShortcutIcon({ "
  },
  {
    "path": "app/components/Icons/SquareBracketsIcon.tsx",
    "chars": 597,
    "preview": "export function SquareBracketsIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.cl"
  },
  {
    "path": "app/components/Icons/StringIcon.tsx",
    "chars": 1566,
    "preview": "export function StringIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.className}"
  },
  {
    "path": "app/components/Icons/SunIcon.tsx",
    "chars": 1867,
    "preview": "import { motion } from \"framer-motion\";\nimport { transition } from \"../../utilities/animationConstants\";\n\nexport const S"
  },
  {
    "path": "app/components/Icons/TreeIcon.tsx",
    "chars": 3023,
    "preview": "export function TreeIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.className}\n "
  },
  {
    "path": "app/components/Icons/TwitterIcon.tsx",
    "chars": 2549,
    "preview": "export function TwitterIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.className"
  },
  {
    "path": "app/components/IndentPreference.tsx",
    "chars": 1254,
    "preview": "import React from \"react\";\nimport { Body } from \"./Primitives/Body\";\nimport { usePreferences } from \"~/components/Prefer"
  },
  {
    "path": "app/components/InfoHeader.tsx",
    "chars": 4543,
    "preview": "import { inferType } from \"@jsonhero/json-infer-types\";\nimport { JSONHeroPath } from \"@jsonhero/path\";\nimport { useCallb"
  },
  {
    "path": "app/components/InfoPanel.tsx",
    "chars": 1154,
    "preview": "import { PreviewValue } from \"./Preview/PreviewValue\";\nimport { RelatedValues } from \"./RelatedValues\";\nimport { Propert"
  },
  {
    "path": "app/components/JsonColumnView.tsx",
    "chars": 1248,
    "preview": "import {\n  useJsonColumnViewAPI,\n  useJsonColumnViewState,\n} from \"../hooks/useJsonColumnView\";\nimport { useHotkeys } fr"
  },
  {
    "path": "app/components/JsonEditor.tsx",
    "chars": 2417,
    "preview": "import { CodeEditor } from \"./CodeEditor\";\nimport { useJson } from \"~/hooks/useJson\";\nimport { useCallback, useMemo, use"
  },
  {
    "path": "app/components/JsonPreview.tsx",
    "chars": 4939,
    "preview": "import { RangeSetBuilder } from \"@codemirror/rangeset\";\nimport { JSONHeroPath } from \"@jsonhero/path\";\nimport {\n  useCod"
  },
  {
    "path": "app/components/JsonSchemaViewer.tsx",
    "chars": 1820,
    "preview": "import { JSONHeroPath } from \"@jsonhero/path\";\nimport { useMemo, useState } from \"react\";\nimport { useJsonSchema } from "
  },
  {
    "path": "app/components/JsonTreeView.tsx",
    "chars": 7649,
    "preview": "import { ChevronDownIcon, ChevronRightIcon } from \"@heroicons/react/outline\";\nimport { useEffect, useRef } from \"react\";"
  },
  {
    "path": "app/components/JsonView.tsx",
    "chars": 767,
    "preview": "import React from \"react\";\nimport { PathBar, PathHistoryControls } from \"./PathBar\";\nimport { SearchBar } from \"./Search"
  },
  {
    "path": "app/components/NewDocument.tsx",
    "chars": 603,
    "preview": "import { DragAndDropForm } from \"./DragAndDropForm\";\nimport { Title } from \"./Primitives/Title\";\nimport { SampleUrls } f"
  },
  {
    "path": "app/components/NewFile.tsx",
    "chars": 480,
    "preview": "import { DragAndDropForm } from \"./DragAndDropForm\";\nimport { Title } from \"./Primitives/Title\";\nimport { SampleUrls } f"
  },
  {
    "path": "app/components/OpenInWindow.tsx",
    "chars": 317,
    "preview": "export type OpenInNewWindowProps = {\n  children?: React.ReactNode;\n  url?: string;\n  className?: string;\n};\n\nexport func"
  },
  {
    "path": "app/components/PathBar.tsx",
    "chars": 6538,
    "preview": "import {\n  ChevronRightIcon,\n  ArrowLeftIcon,\n  ArrowRightIcon,\n  PencilAltIcon,\n  CheckIcon,\n} from \"@heroicons/react/o"
  },
  {
    "path": "app/components/PathPreview.tsx",
    "chars": 3999,
    "preview": "import { ChevronRightIcon, EyeIcon } from \"@heroicons/react/outline\";\nimport { useMemo } from \"react\";\nimport { useJsonC"
  },
  {
    "path": "app/components/PreferencesProvider.tsx",
    "chars": 1620,
    "preview": "import {createContext, Dispatch, ReactNode, SetStateAction, useContext, useEffect, useState} from 'react';\n\ninterface Pr"
  },
  {
    "path": "app/components/Preview/CalendarMonth.tsx",
    "chars": 4419,
    "preview": "import { useMemo } from \"react\";\nimport { Title } from \"../Primitives/Title\";\n\nexport type CalendarMonthProps = {\n  date"
  },
  {
    "path": "app/components/Preview/PreviewBox.tsx",
    "chars": 783,
    "preview": "import { useCallback } from \"react\";\nimport { Title } from \"../Primitives/Title\";\n\nexport type PreviewBoxProps = {\n  lin"
  },
  {
    "path": "app/components/Preview/PreviewProperties.tsx",
    "chars": 793,
    "preview": "import { Body } from \"../Primitives/Body\";\n\nexport type PreviewPropertyProps = {\n  properties: Array<PreviewProperty>;\n}"
  },
  {
    "path": "app/components/Preview/PreviewValue.tsx",
    "chars": 346,
    "preview": "import { useSelectedInfo } from \"~/hooks/useSelectedInfo\";\nimport { PreviewString } from \"./Types/PreviewString\";\n\nexpor"
  },
  {
    "path": "app/components/Preview/Types/PreviewAudioUri.tsx",
    "chars": 849,
    "preview": "import { useRef } from \"react\";\nimport { useHotkeys } from \"react-hotkeys-hook\";\nimport { Body } from \"~/components/Prim"
  },
  {
    "path": "app/components/Preview/Types/PreviewDate.tsx",
    "chars": 1264,
    "preview": "import { Temporal } from \"@js-temporal/polyfill\";\nimport { JSONDateTimeFormat, JSONStringType } from \"@jsonhero/json-inf"
  },
  {
    "path": "app/components/Preview/Types/PreviewHtml.tsx",
    "chars": 867,
    "preview": "import { Body } from \"~/components/Primitives/Body\";\nimport { Title } from \"~/components/Primitives/Title\";\nimport { Pre"
  },
  {
    "path": "app/components/Preview/Types/PreviewIPFSImage.tsx",
    "chars": 463,
    "preview": "import { PreviewImageUri } from \"./PreviewImageUri\";\n\nexport function PreviewIPFSImage({ src }: { src: URL }) {\n  const "
  },
  {
    "path": "app/components/Preview/Types/PreviewImage.tsx",
    "chars": 832,
    "preview": "import { formatBytes } from \"~/utilities/formatter\";\nimport { PreviewBox } from \"../PreviewBox\";\nimport { PreviewPropert"
  },
  {
    "path": "app/components/Preview/Types/PreviewImageUri.tsx",
    "chars": 388,
    "preview": "import { Body } from \"~/components/Primitives/Body\";\nimport { PreviewBox } from \"../PreviewBox\";\n\nexport function Previe"
  },
  {
    "path": "app/components/Preview/Types/PreviewJson.tsx",
    "chars": 1729,
    "preview": "import { useState } from \"react\";\nimport { CodeViewer } from \"~/components/CodeViewer\";\nimport { CopyTextButton } from \""
  },
  {
    "path": "app/components/Preview/Types/PreviewString.tsx",
    "chars": 3301,
    "preview": "import { JSONStringType } from \"@jsonhero/json-infer-types/lib/@types\";\nimport {\n  JSONColorFormat,\n  JSONJSONFormat,\n} "
  },
  {
    "path": "app/components/Preview/Types/PreviewUri.tsx",
    "chars": 1625,
    "preview": "import { JSONStringType } from \"@jsonhero/json-infer-types/lib/@types\";\nimport { useEffect } from \"react\";\nimport { useF"
  },
  {
    "path": "app/components/Preview/Types/PreviewUriElement.tsx",
    "chars": 586,
    "preview": "import { PreviewInfo } from \"./preview.types\";\nimport { PreviewHtml } from \"./PreviewHtml\";\nimport { PreviewImage } from"
  },
  {
    "path": "app/components/Preview/Types/PreviewVideoUri.tsx",
    "chars": 902,
    "preview": "import { useRef } from \"react\";\nimport { useHotkeys } from \"react-hotkeys-hook\";\nimport { Body } from \"~/components/Prim"
  },
  {
    "path": "app/components/Preview/Types/RetweetIcon.tsx",
    "chars": 881,
    "preview": "export function RetweetIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      className={props.className"
  },
  {
    "path": "app/components/Preview/Types/preview.types.d.ts",
    "chars": 7319,
    "preview": "export declare type PreviewImage = {\n  url: string;\n  contentType: \"image\" | \"gif\";\n  mimeType: string;\n  size?: number;"
  },
  {
    "path": "app/components/Primitives/Body.tsx",
    "chars": 221,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const Body: FunctionComponent<{ className?: string }> = ({\n  classNam"
  },
  {
    "path": "app/components/Primitives/BodyBold.tsx",
    "chars": 245,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const BodyBold: FunctionComponent<{ className?: string }> = ({\n  clas"
  },
  {
    "path": "app/components/Primitives/ExtraLargeTitle.tsx",
    "chars": 253,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const ExtraLargeTitle: FunctionComponent<{ className?: string }> = ({"
  },
  {
    "path": "app/components/Primitives/LargeMono.tsx",
    "chars": 224,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const LargeMono: FunctionComponent<{ className?: string }> = ({\n  cla"
  },
  {
    "path": "app/components/Primitives/LargeTitle.tsx",
    "chars": 248,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const LargeTitle: FunctionComponent<{ className?: string }> = ({\n  cl"
  },
  {
    "path": "app/components/Primitives/Mono.tsx",
    "chars": 219,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const Mono: FunctionComponent<{ className?: string }> = ({\n  classNam"
  },
  {
    "path": "app/components/Primitives/PageNotFoundTitle.tsx",
    "chars": 255,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const PageNotFoundTitle: FunctionComponent<{ className?: string }> = "
  },
  {
    "path": "app/components/Primitives/SmallBody.tsx",
    "chars": 224,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const SmallBody: FunctionComponent<{ className?: string }> = ({\n  cla"
  },
  {
    "path": "app/components/Primitives/SmallSubtitle.tsx",
    "chars": 255,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const SmallSubtitle: FunctionComponent<{ className?: string }> = ({\n "
  },
  {
    "path": "app/components/Primitives/SmallTitle.tsx",
    "chars": 247,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const SmallTitle: FunctionComponent<{ className?: string }> = ({\n  cl"
  },
  {
    "path": "app/components/Primitives/Title.tsx",
    "chars": 242,
    "preview": "import { FunctionComponent } from \"react\";\n\nexport const Title: FunctionComponent<{ className?: string }> = ({\n  classNa"
  },
  {
    "path": "app/components/Properties/PropertiesFloat.tsx",
    "chars": 613,
    "preview": "import { JSONFloatType } from \"@jsonhero/json-infer-types\";\nimport { formatValue } from \"~/utilities/formatter\";\nimport "
  },
  {
    "path": "app/components/Properties/PropertiesInt.tsx",
    "chars": 1716,
    "preview": "import { JSONIntType } from \"@jsonhero/json-infer-types\";\nimport {\n  JSONTimestampFormat,\n} from \"@jsonhero/json-infer-t"
  },
  {
    "path": "app/components/Properties/PropertiesString.tsx",
    "chars": 5203,
    "preview": "import { JSONStringType, JSONURIFormat } from \"@jsonhero/json-infer-types\";\nimport {\n  JSONColorFormat,\n  JSONDateTimeFo"
  },
  {
    "path": "app/components/Properties/PropertiesValue.tsx",
    "chars": 578,
    "preview": "import { useSelectedInfo } from \"~/hooks/useSelectedInfo\";\nimport { PropertiesInt } from \"./PropertiesInt\";\nimport { Pro"
  },
  {
    "path": "app/components/RelatedValues.tsx",
    "chars": 3715,
    "preview": "import { useMemo, useState } from \"react\";\nimport { Title } from \"~/components/Primitives/Title\";\nimport { useJson } fro"
  },
  {
    "path": "app/components/Resizable.tsx",
    "chars": 2567,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\n\ntype ResizableProps = {\n  children: React.ReactNode;\n  isHo"
  },
  {
    "path": "app/components/SampleUrls.tsx",
    "chars": 581,
    "preview": "import { ExampleDoc } from \"./ExampleDoc\";\n\nexport function SampleUrls() {\n  return (\n    <div className=\"flex justify-s"
  },
  {
    "path": "app/components/SearchBar.tsx",
    "chars": 2543,
    "preview": "import { SearchIcon } from \"@heroicons/react/outline\";\nimport { ShortcutIcon } from \"./Icons/ShortcutIcon\";\nimport { Bod"
  },
  {
    "path": "app/components/SearchPalette.tsx",
    "chars": 14106,
    "preview": "import { useJsonSearchApi, useJsonSearchState } from \"~/hooks/useJsonSearch\";\nimport {\n  ChevronRightIcon,\n  Exclamation"
  },
  {
    "path": "app/components/Share.tsx",
    "chars": 3214,
    "preview": "import React, { useCallback, useEffect, useState } from \"react\";\nimport { Body } from \"./Primitives/Body\";\nimport { Clip"
  },
  {
    "path": "app/components/SideBar.tsx",
    "chars": 4047,
    "preview": "import { TemplateIcon, CodeIcon, DownloadIcon } from \"@heroicons/react/outline\";\nimport { TreeIcon } from \"~/components/"
  },
  {
    "path": "app/components/StarCountProvider.tsx",
    "chars": 538,
    "preview": "import { createContext, useContext } from \"react\";\nimport type { ReactNode } from \"react\";\n\nexport type StarCountType = "
  },
  {
    "path": "app/components/ThemeModeToggle.tsx",
    "chars": 787,
    "preview": "import { useHotkeys } from \"react-hotkeys-hook\";\nimport { MoonIcon } from \"./Icons/MoonIcon\";\nimport { SunIcon } from \"."
  },
  {
    "path": "app/components/ThemeProvider.tsx",
    "chars": 2733,
    "preview": "import { createContext, useContext, useEffect, useRef, useState } from \"react\";\nimport type { Dispatch, ReactNode, SetSt"
  },
  {
    "path": "app/components/ToolTip.tsx",
    "chars": 3167,
    "preview": "import React, { useState } from \"react\";\nimport { motion } from \"framer-motion\";\n\nexport type ToolTipProps = {\n  childre"
  },
  {
    "path": "app/components/UI/Dialog.tsx",
    "chars": 910,
    "preview": "import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport React, { ComponentPropsWithoutRef } from \"react\";\nimpo"
  },
  {
    "path": "app/components/UI/GithubStar.tsx",
    "chars": 1076,
    "preview": "import { formatStarCount } from \"~/utilities/formatStarCount\";\nimport { GithubIconSimple } from \"../Icons/GithubIconSimp"
  },
  {
    "path": "app/components/UI/GithubStarSmall.tsx",
    "chars": 1152,
    "preview": "import { formatStarCount } from \"~/utilities/formatStarCount\";\nimport { GithubIconSimple } from \"../Icons/GithubIconSimp"
  },
  {
    "path": "app/components/UI/Popover.tsx",
    "chars": 837,
    "preview": "import * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport type { ComponentPropsWithoutRef } from \"@radix-ui/re"
  },
  {
    "path": "app/components/UI/Tabs.tsx",
    "chars": 1414,
    "preview": "import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport type { ComponentPropsWithoutRef } from \"@radix-ui/react-pr"
  },
  {
    "path": "app/components/UI/ToastPopover.tsx",
    "chars": 2343,
    "preview": "import {\n  ExclamationCircleIcon,\n  InformationCircleIcon,\n} from \"@heroicons/react/outline\";\nimport * as ToastPrimitive"
  },
  {
    "path": "app/components/UrlForm.tsx",
    "chars": 1559,
    "preview": "import { useState } from \"react\";\nimport { Form, useTransition } from \"remix\";\n\nexport type UrlFormProps = {\n  className"
  },
  {
    "path": "app/components/ValueIcon.tsx",
    "chars": 4684,
    "preview": "import { FunctionComponent } from \"react\";\nimport {\n  ArchiveIcon,\n  AtSymbolIcon,\n  CalendarIcon,\n  ChatAlt2Icon,\n  Che"
  },
  {
    "path": "app/components/json-schema-map.d.ts",
    "chars": 830,
    "preview": "declare module \"json-source-map\" {\n  export interface ParseOptions {\n    bigint?: boolean;\n  }\n\n  export type PointerPro"
  },
  {
    "path": "app/entry.client.tsx",
    "chars": 263,
    "preview": "import { hydrate } from \"react-dom\";\nimport { RemixBrowser } from \"remix\";\nimport { load } from \"fathom-client\";\n\nhydrat"
  },
  {
    "path": "app/entry.server.tsx",
    "chars": 556,
    "preview": "import { renderToString } from \"react-dom/server\";\nimport { RemixServer } from \"remix\";\nimport type { EntryContext } fro"
  },
  {
    "path": "app/entry.worker.ts",
    "chars": 1723,
    "preview": "/// <reference lib=\"WebWorker\" />\nimport { JSONHeroSearch } from \"@jsonhero/fuzzy-json-search\";\nimport { inferType } fro"
  },
  {
    "path": "app/graphJSON.server.ts",
    "chars": 221,
    "preview": "export async function sendEvent(event: Record<string, any>): Promise<void> {\n  return;\n}\n\nfunction graphJsonReplacer(key"
  },
  {
    "path": "app/hooks/useClickOutside.tsx",
    "chars": 775,
    "preview": "import { RefObject, useEffect, useRef } from \"react\";\n\nexport function useClickOutside(\n  elementRef: RefObject<HTMLElem"
  },
  {
    "path": "app/hooks/useIsMounted.tsx",
    "chars": 293,
    "preview": "import { useRef, useEffect, useCallback } from \"react\";\n\nexport function useIsMounted(): () => boolean {\n  const ref = u"
  },
  {
    "path": "app/hooks/useJson.tsx",
    "chars": 911,
    "preview": "import {\n  createContext,\n  Dispatch,\n  ReactNode,\n  SetStateAction,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\""
  },
  {
    "path": "app/hooks/useJsonColumnView.tsx",
    "chars": 5243,
    "preview": "import { JSONHeroPath } from \"@jsonhero/path\";\nimport { pick } from \"lodash-es\";\nimport React, { useEffect, useRef } fro"
  },
  {
    "path": "app/hooks/useJsonDoc.tsx",
    "chars": 789,
    "preview": "import { createContext, ReactNode, useContext } from \"react\";\nimport invariant from \"tiny-invariant\";\nimport { JSONDocum"
  },
  {
    "path": "app/hooks/useJsonSchema.tsx",
    "chars": 862,
    "preview": "import { Schema } from \"@jsonhero/json-schema-fns\";\nimport { inferSchema } from \"@jsonhero/schema-infer\";\nimport { creat"
  },
  {
    "path": "app/hooks/useJsonSearch.tsx",
    "chars": 5677,
    "preview": "import { useJson } from \"./useJson\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useReducer,\n "
  },
  {
    "path": "app/hooks/useJsonTree.tsx",
    "chars": 3468,
    "preview": "import { useJson } from \"./useJson\";\nimport { inferType, JSONValueType } from \"@jsonhero/json-infer-types\";\nimport { JSO"
  },
  {
    "path": "app/hooks/useLoadWhenOnline.tsx",
    "chars": 567,
    "preview": "import { useEffect, useRef } from \"react\";\n\nexport function useLoadWhenOnline(callback: () => void, deps: unknown[] = []"
  },
  {
    "path": "app/hooks/useMemoCompare.ts",
    "chars": 843,
    "preview": "import { useEffect, useRef } from \"react\";\n\nexport function useMemoCompare<T>(\n  next: T | null | undefined,\n  compare: "
  },
  {
    "path": "app/hooks/useOnScreen.tsx",
    "chars": 674,
    "preview": "import { useEffect, useState, useRef, RefObject } from \"react\";\n\nexport function useOnScreen(ref: RefObject<HTMLElement>"
  },
  {
    "path": "app/hooks/useRelatedPaths.ts",
    "chars": 869,
    "preview": "import { useMemo, useRef } from \"react\";\nimport { getRelatedPathsAtPath } from \"~/utilities/relatedValues\";\nimport { use"
  },
  {
    "path": "app/hooks/useSelectedInfo.tsx",
    "chars": 636,
    "preview": "import { inferType, JSONValueType } from \"@jsonhero/json-infer-types\";\nimport { JSONHeroPath } from \"@jsonhero/path\";\nim"
  },
  {
    "path": "app/hooks/useVirtualTree.ts",
    "chars": 18226,
    "preview": "import pick from \"lodash-es/pick\";\nimport React, {\n  useReducer,\n  Reducer,\n  useCallback,\n  Dispatch,\n  useEffect,\n  us"
  },
  {
    "path": "app/jsonDoc.server.ts",
    "chars": 3544,
    "preview": "import { customRandom } from \"nanoid\";\nimport safeFetch from \"./utilities/safeFetch\";\nimport createFromRawXml from \"./ut"
  },
  {
    "path": "app/root.tsx",
    "chars": 3238,
    "preview": "import {\n  Links,\n  LiveReload,\n  LoaderFunction,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n  useLoaderData,\n  u"
  },
  {
    "path": "app/routes/actions/$id/update.ts",
    "chars": 899,
    "preview": "import { ActionFunction, json } from \"remix\";\nimport invariant from \"tiny-invariant\";\nimport { sendEvent } from \"~/graph"
  },
  {
    "path": "app/routes/actions/createFromFile.ts",
    "chars": 1112,
    "preview": "import { ActionFunction, redirect } from \"remix\";\nimport invariant from \"tiny-invariant\";\nimport { sendEvent } from \"~/g"
  },
  {
    "path": "app/routes/actions/createFromUrl.ts",
    "chars": 2467,
    "preview": "import { redirect } from \"remix\";\nimport type { ActionFunction, LoaderFunction } from \"remix\";\nimport invariant from \"ti"
  },
  {
    "path": "app/routes/actions/getPreview.$url.ts",
    "chars": 3801,
    "preview": "import { LoaderFunction } from \"remix\";\nimport invariant from \"tiny-invariant\";\nimport { getUriPreview } from \"~/service"
  },
  {
    "path": "app/routes/actions/setTheme.ts",
    "chars": 958,
    "preview": "import { json, redirect } from \"remix\";\nimport type { ActionFunction, LoaderFunction } from \"remix\";\nimport { getThemeSe"
  },
  {
    "path": "app/routes/api/create[.json].ts",
    "chars": 1805,
    "preview": "import { ActionFunction, json, LoaderFunction } from \"remix\";\nimport invariant from \"tiny-invariant\";\nimport { sendEvent"
  },
  {
    "path": "app/routes/index.tsx",
    "chars": 1860,
    "preview": "import { HomeCollaborateSection } from \"~/components/Home/HomeCollaborateSection\";\nimport { HomeEdgeCasesSection } from "
  },
  {
    "path": "app/routes/j/$id/editor.tsx",
    "chars": 121,
    "preview": "import { JsonEditor } from \"~/components/JsonEditor\";\n\nexport default function EditorView() {\n  return <JsonEditor />;\n}"
  },
  {
    "path": "app/routes/j/$id/index.tsx",
    "chars": 134,
    "preview": "import { JsonColumnView } from \"~/components/JsonColumnView\";\n\nexport default function DefaultView() {\n  return <JsonCol"
  },
  {
    "path": "app/routes/j/$id/terminal.tsx",
    "chars": 867,
    "preview": "import { LargeTitle } from \"~/components/Primitives/LargeTitle\";\nimport { Body } from \"~/components/Primitives/Body\";\nim"
  },
  {
    "path": "app/routes/j/$id/tree.tsx",
    "chars": 129,
    "preview": "import { JsonTreeView } from \"~/components/JsonTreeView\";\n\nexport default function TreeViewPage() {\n  return <JsonTreeVi"
  },
  {
    "path": "app/routes/j/$id.tsx",
    "chars": 8331,
    "preview": "import {\n  ActionFunction,\n  LoaderFunction,\n  MetaFunction,\n  Outlet,\n  redirect, ThrownResponse, useCatch,\n  useLoader"
  },
  {
    "path": "app/routes/j/$id[.json].ts",
    "chars": 549,
    "preview": "import { json, LoaderFunction } from \"remix\";\nimport invariant from \"tiny-invariant\";\nimport { getDocument } from \"~/jso"
  },
  {
    "path": "app/routes/new.tsx",
    "chars": 1953,
    "preview": "import { json, LoaderFunction, redirect } from \"remix\";\nimport invariant from \"tiny-invariant\";\nimport { sendEvent } fro"
  },
  {
    "path": "app/routes/privacy.mdx",
    "chars": 19110,
    "preview": "---\nmeta:\n  title: JSON Hero - Privacy\n  og:title: JSON Hero - Privacy\n---\n\nimport { HomeHeader } from \"~/components/Hom"
  },
  {
    "path": "app/services/apihero.server.ts",
    "chars": 243,
    "preview": "import { createFetchProxy } from \"@apihero/fetch\";\n\nexport const fetchProxy =\n  typeof APIHERO_PROJECT_KEY === \"string\"\n"
  },
  {
    "path": "app/services/github.server.ts",
    "chars": 862,
    "preview": "export async function getStarCount(): Promise<number | undefined> {\n  try {\n    const response = await fetch(\n      `htt"
  },
  {
    "path": "app/services/toast.server.ts",
    "chars": 882,
    "preview": "import { createCookieSessionStorage, Session } from \"remix\";\n\nexport type ToastMessage = {\n  message: string;\n  title: s"
  },
  {
    "path": "app/services/uriPreview.server.ts",
    "chars": 5173,
    "preview": "import {\n  PreviewImage,\n  PreviewJson,\n  PreviewResult,\n  OpenGraphPreviewData,\n  OpenGraphPreviewDataError\n} from \"~/c"
  },
  {
    "path": "app/theme.server.ts",
    "chars": 782,
    "preview": "import { createCookieSessionStorage } from \"remix\";\n\nimport { Theme, isTheme } from \"~/components/ThemeProvider\";\n\nconst"
  },
  {
    "path": "app/useColumnView/index.ts",
    "chars": 13812,
    "preview": "import { omit } from \"lodash-es\";\nimport React, {\n  ReactNode,\n  Reducer,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRe"
  },
  {
    "path": "app/utilities/animationConstants.ts",
    "chars": 141,
    "preview": "export const transition = {\n  type: \"spring\",\n  stiffness: 200,\n  damping: 10,\n};\n\nexport const whileTap = {\n  scale: 0."
  },
  {
    "path": "app/utilities/classnames.ts",
    "chars": 107,
    "preview": "export default function classnames(...args: string[]): string {\n  return args.filter(Boolean).join(\" \");\n}\n"
  },
  {
    "path": "app/utilities/codeMirrorSetup.ts",
    "chars": 1098,
    "preview": "import {\n  highlightSpecialChars,\n  drawSelection,\n  highlightActiveLine,\n  dropCursor,\n} from \"@codemirror/view\";\nimpor"
  },
  {
    "path": "app/utilities/codeMirrorTheme.ts",
    "chars": 8258,
    "preview": "import { EditorView } from \"@codemirror/view\";\nimport { Extension } from \"@codemirror/state\";\nimport { HighlightStyle, t"
  },
  {
    "path": "app/utilities/colors.ts",
    "chars": 820,
    "preview": "import { inferType } from \"@jsonhero/json-infer-types\";\nimport { JSONHeroPath } from \"@jsonhero/path\";\n\nexport function "
  },
  {
    "path": "app/utilities/dataType.ts",
    "chars": 1074,
    "preview": "import { JSONValueType } from \"@jsonhero/json-infer-types\";\n\nexport interface HierarchicalTypes {\n  types: string[];\n}\n\n"
  },
  {
    "path": "app/utilities/formatStarCount.ts",
    "chars": 593,
    "preview": "// Should truncate the number to not show the exact number of stars\n// If over 1000, show 1k\n// Round up to the nearest "
  },
  {
    "path": "app/utilities/formatter.ts",
    "chars": 3513,
    "preview": "import { Temporal } from \"@js-temporal/polyfill\";\nimport { inferTemporal } from \"./inferredTemporal\";\n\nimport {\n  JSONDa"
  },
  {
    "path": "app/utilities/getRandomUserAgent.ts",
    "chars": 12496,
    "preview": "const userAgents = [\n  \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0."
  },
  {
    "path": "app/utilities/icons.ts",
    "chars": 3696,
    "preview": "import {\n  CubeIcon,\n  CollectionIcon,\n  EyeOffIcon,\n  CheckCircleIcon,\n  AnnotationIcon,\n  CalendarIcon,\n  AtSymbolIcon"
  },
  {
    "path": "app/utilities/inferredTemporal.ts",
    "chars": 1183,
    "preview": "import { Temporal } from \"@js-temporal/polyfill\";\nimport { JSONDateTimeFormat } from \"@jsonhero/json-infer-types\";\n\nexpo"
  },
  {
    "path": "app/utilities/jsonColumnView.ts",
    "chars": 6142,
    "preview": "import { inferType, JSONValueType } from \"@jsonhero/json-infer-types\";\nimport { JSONHeroPath, PathComponent } from \"@jso"
  },
  {
    "path": "app/utilities/nullable.ts",
    "chars": 285,
    "preview": "import { JSONHeroPath } from \"@jsonhero/path\";\n\nexport function isNullable(relatedPaths: string[], json: unknown): boole"
  },
  {
    "path": "app/utilities/relatedValues.ts",
    "chars": 2592,
    "preview": "import { inferType } from \"@jsonhero/json-infer-types\";\nimport { JSONHeroPath } from \"@jsonhero/path\";\nimport { groupBy,"
  },
  {
    "path": "app/utilities/safeFetch.ts",
    "chars": 330,
    "preview": "export default function safeFetch(\n  url: string,\n  options: RequestInit = {}\n): Promise<Response> {\n  return fetch(url,"
  },
  {
    "path": "app/utilities/search.ts",
    "chars": 14125,
    "preview": "import { Array, Dict } from \"@swan-io/boxed\";\nimport { groupBy, uniq } from \"lodash-es\";\n\ntype StringSlice = {\n  isMatch"
  },
  {
    "path": "app/utilities/stableJson.ts",
    "chars": 1178,
    "preview": "export function stableJson(json: unknown, keyOrder: string[] = []): unknown {\n  if (\n    Array.isArray(json) &&\n    json"
  },
  {
    "path": "app/utilities/xml/__test__/convertXmlToJsonString.test.ts",
    "chars": 837,
    "preview": "import * as fs from \"fs\";\nimport { join } from \"path\";\nimport convertFromRawXml from \"../convertFromRawXml\";\n\nconst xmlS"
  },
  {
    "path": "app/utilities/xml/__test__/isXML.test.ts",
    "chars": 561,
    "preview": "import * as fs from \"fs\";\nimport { join } from \"path\";\nimport isXML from \"../isXML\";\n\nconst xmlString = fs.readFileSync("
  },
  {
    "path": "app/utilities/xml/__test__/xml.txt",
    "chars": 1411,
    "preview": "<!-- <?xml version = \"1.0\"?>\n<!DOCTYPE ORDERFILE [\n<!ELEMENT ORDERFILE (CUSTOMER)*>\n<!ELEMENT CUSTOMER (NAME, DATE, ORDE"
  },
  {
    "path": "app/utilities/xml/convertFromRawXml.ts",
    "chars": 3106,
    "preview": "import { DOMParser } from \"@xmldom/xmldom\";\n\nexport type SerializedXMLObject = {\n  [key: string]: [] | string | {} | und"
  },
  {
    "path": "app/utilities/xml/createFromRawXml.ts",
    "chars": 411,
    "preview": "import {\n  createFromRawJson,\n  CreateJsonOptions,\n  JSONDocument,\n} from \"~/jsonDoc.server\";\nimport convertFromRawXml f"
  },
  {
    "path": "app/utilities/xml/isXML.ts",
    "chars": 891,
    "preview": "import { DOMParser } from \"@xmldom/xmldom\";\n\nexport default function isXML(possibleXml: string): boolean {\n  let isValid"
  },
  {
    "path": "examples/owenWilsonWows.json",
    "chars": 23975,
    "preview": "[\n  {\n    \"movie\": \"Cars 3\",\n    \"year\": 2017,\n    \"release_date\": \"2017-05-23\",\n    \"director\": \"Brian Fee\",\n    \"chara"
  },
  {
    "path": "examples/pokemon.json",
    "chars": 32253,
    "preview": "{\n  \"abilities\": [\n    {\n      \"ability\": {\n        \"name\": \"limber\",\n        \"url\": \"https://pokeapi.co/api/v2/ability/"
  },
  {
    "path": "examples/ronSwansonQuotes.json",
    "chars": 1596,
    "preview": "[\n  \"There has never been a sadness that can’t been cured by breakfast food.\",\n  \"Dear frozen yogurt, you are the celery"
  }
]

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

About this extraction

This page contains the full source code of the triggerdotdev/jsonhero-web GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 215 files (517.7 KB), approximately 161.1k tokens, and a symbol index with 434 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!