Teal.fm frontend powered by slices.network tealfm-slices.wisp.place
tealfm slices

🎷

+24
.gitignore
···
+
# Logs
+
logs
+
*.log
+
npm-debug.log*
+
yarn-debug.log*
+
yarn-error.log*
+
pnpm-debug.log*
+
lerna-debug.log*
+
+
node_modules
+
dist
+
dist-ssr
+
*.local
+
+
# Editor directories and files
+
.vscode/*
+
!.vscode/extensions.json
+
.idea
+
.DS_Store
+
*.suo
+
*.ntvs*
+
*.njsproj
+
*.sln
+
*.sw?
+131
README.md
···
+
# Slices Teal Relay Demo
+
+
A teal.fm timeline built with React, Relay, and Tailwind CSS. Displays music
+
plays from the Slices GraphQL API.
+
+
## Features
+
+
- Real-time music scrobble feed
+
- User profiles with listening history
+
- Album artwork from MusicBrainz
+
- Infinite scroll pagination
+
- Dark mode UI inspired by Last.fm
+
- Powered by Relay for efficient GraphQL data management
+
+
## Tech Stack
+
+
- **React** - UI framework
+
- **Relay** - GraphQL client with automatic cache management
+
- **React Router** - Client-side routing
+
- **Tailwind CSS** - Styling
+
- **Vite** - Build tool
+
- **TypeScript** - Type safety
+
+
## Prerequisites
+
+
- Node.js 18+ or Deno
+
- npm or pnpm
+
+
## Getting Started
+
+
1. **Install dependencies**
+
+
```bash
+
npm install
+
```
+
+
2. **Fetch the GraphQL schema**
+
+
```bash
+
npm run schema
+
```
+
+
3. **Generate Relay types**
+
+
```bash
+
npx relay-compiler
+
```
+
+
4. **Start the development server**
+
+
```bash
+
npm run dev
+
```
+
+
The app will be available at `http://localhost:5173`
+
+
## Development
+
+
### Updating the GraphQL Schema
+
+
The project connects to the Slices API. To update the schema:
+
+
```bash
+
npm run schema
+
npx relay-compiler
+
```
+
+
### Project Structure
+
+
```
+
src/
+
├── App.tsx # Main feed component
+
├── Profile.tsx # User profile page
+
├── TrackItem.tsx # Individual track card component
+
├── LoadingFallback.tsx # Loading skeleton
+
├── useAlbumArt.ts # Hook to fetch album art from MusicBrainz
+
└── __generated__/ # Relay-generated TypeScript types
+
```
+
+
### Working with Relay
+
+
This project uses Relay's modern API with:
+
+
- **`useLazyLoadQuery`** - Load data on component mount
+
- **`usePaginationFragment`** - Infinite scroll pagination with automatic cache
+
updates
+
- **`useFragment`** - Fragment composition for data colocation
+
- **`@connection`** directive - Automatic list merging for pagination
+
+
After modifying any GraphQL queries or fragments, run:
+
+
```bash
+
npx relay-compiler
+
```
+
+
### Environment
+
+
The app connects to:
+
+
- **Production API**: `https://api.slices.network/graphql`
+
- **Slice**:
+
`at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a`
+
+
## Scripts
+
+
- `npm run dev` - Start development server
+
- `npm run build` - Build for production
+
- `npm run preview` - Preview production build
+
- `npm run lint` - Run ESLint
+
- `npm run schema` - Fetch GraphQL schema from production API
+
+
## Features in Detail
+
+
### Album Artwork
+
+
The app fetches album artwork from MusicBrainz using the `releaseMbId` field.
+
The fetch happens asynchronously per track and displays when available.
+
+
### Infinite Scroll
+
+
Both the main feed and profile pages use Relay's `usePaginationFragment` for
+
efficient infinite scrolling with automatic cache management.
+
+
### Routing
+
+
- `/` - Main feed
+
- `/profile/:handle` - User profile page
+
+
## License
+
+
MIT
+2030
deno.lock
···
+
{
+
"version": "5",
+
"specifiers": {
+
"npm:@eslint/js@^9.36.0": "9.36.0",
+
"npm:@tailwindcss/postcss@^4.1.14": "4.1.14",
+
"npm:@types/node@^24.6.0": "24.6.2",
+
"npm:@types/react-dom@^19.1.9": "19.2.0_@types+react@19.2.0",
+
"npm:@types/react-relay@^18.2.1": "18.2.1",
+
"npm:@types/react@^19.1.16": "19.2.0",
+
"npm:@types/relay-runtime@^19.0.3": "19.0.3",
+
"npm:@vitejs/plugin-react@^5.0.4": "5.0.4_vite@7.1.8__@types+node@24.6.2__picomatch@4.0.3_@babel+core@7.28.4_@types+node@24.6.2",
+
"npm:autoprefixer@^10.4.21": "10.4.21_postcss@8.5.6",
+
"npm:babel-plugin-relay@^20.1.1": "20.1.1",
+
"npm:eslint-plugin-react-hooks@^5.2.0": "5.2.0_eslint@9.36.0",
+
"npm:eslint-plugin-react-refresh@~0.4.22": "0.4.23_eslint@9.36.0",
+
"npm:eslint@^9.36.0": "9.36.0",
+
"npm:globals@^16.4.0": "16.4.0",
+
"npm:graphql@^16.11.0": "16.11.0",
+
"npm:postcss@^8.5.6": "8.5.6",
+
"npm:react-dom@^19.1.1": "19.2.0_react@19.2.0",
+
"npm:react-relay@^20.1.1": "20.1.1_react@19.2.0",
+
"npm:react-router-dom@^7.9.3": "7.9.3_react@19.2.0_react-dom@19.2.0__react@19.2.0",
+
"npm:react@^19.1.1": "19.2.0",
+
"npm:relay-compiler@^20.1.1": "20.1.1",
+
"npm:relay-runtime@^20.1.1": "20.1.1",
+
"npm:tailwindcss@^4.1.14": "4.1.14",
+
"npm:typescript-eslint@^8.45.0": "8.45.0_eslint@9.36.0_typescript@5.9.3_@typescript-eslint+parser@8.45.0__eslint@9.36.0__typescript@5.9.3",
+
"npm:typescript@~5.9.3": "5.9.3",
+
"npm:vite@^7.1.7": "7.1.8_@types+node@24.6.2_picomatch@4.0.3"
+
},
+
"npm": {
+
"@alloc/quick-lru@5.2.0": {
+
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="
+
},
+
"@babel/code-frame@7.27.1": {
+
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+
"dependencies": [
+
"@babel/helper-validator-identifier",
+
"js-tokens",
+
"picocolors"
+
]
+
},
+
"@babel/compat-data@7.28.4": {
+
"integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw=="
+
},
+
"@babel/core@7.28.4": {
+
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
+
"dependencies": [
+
"@babel/code-frame",
+
"@babel/generator",
+
"@babel/helper-compilation-targets",
+
"@babel/helper-module-transforms",
+
"@babel/helpers",
+
"@babel/parser",
+
"@babel/template",
+
"@babel/traverse",
+
"@babel/types",
+
"@jridgewell/remapping",
+
"convert-source-map",
+
"debug",
+
"gensync",
+
"json5",
+
"semver@6.3.1"
+
]
+
},
+
"@babel/generator@7.28.3": {
+
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+
"dependencies": [
+
"@babel/parser",
+
"@babel/types",
+
"@jridgewell/gen-mapping",
+
"@jridgewell/trace-mapping",
+
"jsesc"
+
]
+
},
+
"@babel/helper-compilation-targets@7.27.2": {
+
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+
"dependencies": [
+
"@babel/compat-data",
+
"@babel/helper-validator-option",
+
"browserslist",
+
"lru-cache",
+
"semver@6.3.1"
+
]
+
},
+
"@babel/helper-globals@7.28.0": {
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="
+
},
+
"@babel/helper-module-imports@7.27.1": {
+
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+
"dependencies": [
+
"@babel/traverse",
+
"@babel/types"
+
]
+
},
+
"@babel/helper-module-transforms@7.28.3_@babel+core@7.28.4": {
+
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+
"dependencies": [
+
"@babel/core",
+
"@babel/helper-module-imports",
+
"@babel/helper-validator-identifier",
+
"@babel/traverse"
+
]
+
},
+
"@babel/helper-plugin-utils@7.27.1": {
+
"integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="
+
},
+
"@babel/helper-string-parser@7.27.1": {
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
+
},
+
"@babel/helper-validator-identifier@7.27.1": {
+
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
+
},
+
"@babel/helper-validator-option@7.27.1": {
+
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="
+
},
+
"@babel/helpers@7.28.4": {
+
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+
"dependencies": [
+
"@babel/template",
+
"@babel/types"
+
]
+
},
+
"@babel/parser@7.28.4": {
+
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
+
"dependencies": [
+
"@babel/types"
+
],
+
"bin": true
+
},
+
"@babel/plugin-transform-react-jsx-self@7.27.1_@babel+core@7.28.4": {
+
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+
"dependencies": [
+
"@babel/core",
+
"@babel/helper-plugin-utils"
+
]
+
},
+
"@babel/plugin-transform-react-jsx-source@7.27.1_@babel+core@7.28.4": {
+
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+
"dependencies": [
+
"@babel/core",
+
"@babel/helper-plugin-utils"
+
]
+
},
+
"@babel/runtime@7.28.4": {
+
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="
+
},
+
"@babel/template@7.27.2": {
+
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+
"dependencies": [
+
"@babel/code-frame",
+
"@babel/parser",
+
"@babel/types"
+
]
+
},
+
"@babel/traverse@7.28.4": {
+
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
+
"dependencies": [
+
"@babel/code-frame",
+
"@babel/generator",
+
"@babel/helper-globals",
+
"@babel/parser",
+
"@babel/template",
+
"@babel/types",
+
"debug"
+
]
+
},
+
"@babel/types@7.28.4": {
+
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
+
"dependencies": [
+
"@babel/helper-string-parser",
+
"@babel/helper-validator-identifier"
+
]
+
},
+
"@emnapi/core@1.5.0": {
+
"integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==",
+
"dependencies": [
+
"@emnapi/wasi-threads",
+
"tslib"
+
]
+
},
+
"@emnapi/runtime@1.5.0": {
+
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
+
"dependencies": [
+
"tslib"
+
]
+
},
+
"@emnapi/wasi-threads@1.1.0": {
+
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+
"dependencies": [
+
"tslib"
+
]
+
},
+
"@esbuild/aix-ppc64@0.25.10": {
+
"integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==",
+
"os": ["aix"],
+
"cpu": ["ppc64"]
+
},
+
"@esbuild/android-arm64@0.25.10": {
+
"integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==",
+
"os": ["android"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/android-arm@0.25.10": {
+
"integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==",
+
"os": ["android"],
+
"cpu": ["arm"]
+
},
+
"@esbuild/android-x64@0.25.10": {
+
"integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==",
+
"os": ["android"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/darwin-arm64@0.25.10": {
+
"integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==",
+
"os": ["darwin"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/darwin-x64@0.25.10": {
+
"integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==",
+
"os": ["darwin"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/freebsd-arm64@0.25.10": {
+
"integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==",
+
"os": ["freebsd"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/freebsd-x64@0.25.10": {
+
"integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==",
+
"os": ["freebsd"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/linux-arm64@0.25.10": {
+
"integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/linux-arm@0.25.10": {
+
"integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@esbuild/linux-ia32@0.25.10": {
+
"integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==",
+
"os": ["linux"],
+
"cpu": ["ia32"]
+
},
+
"@esbuild/linux-loong64@0.25.10": {
+
"integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==",
+
"os": ["linux"],
+
"cpu": ["loong64"]
+
},
+
"@esbuild/linux-mips64el@0.25.10": {
+
"integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==",
+
"os": ["linux"],
+
"cpu": ["mips64el"]
+
},
+
"@esbuild/linux-ppc64@0.25.10": {
+
"integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==",
+
"os": ["linux"],
+
"cpu": ["ppc64"]
+
},
+
"@esbuild/linux-riscv64@0.25.10": {
+
"integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==",
+
"os": ["linux"],
+
"cpu": ["riscv64"]
+
},
+
"@esbuild/linux-s390x@0.25.10": {
+
"integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==",
+
"os": ["linux"],
+
"cpu": ["s390x"]
+
},
+
"@esbuild/linux-x64@0.25.10": {
+
"integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/netbsd-arm64@0.25.10": {
+
"integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==",
+
"os": ["netbsd"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/netbsd-x64@0.25.10": {
+
"integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==",
+
"os": ["netbsd"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/openbsd-arm64@0.25.10": {
+
"integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==",
+
"os": ["openbsd"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/openbsd-x64@0.25.10": {
+
"integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==",
+
"os": ["openbsd"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/openharmony-arm64@0.25.10": {
+
"integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==",
+
"os": ["openharmony"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/sunos-x64@0.25.10": {
+
"integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==",
+
"os": ["sunos"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/win32-arm64@0.25.10": {
+
"integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==",
+
"os": ["win32"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/win32-ia32@0.25.10": {
+
"integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==",
+
"os": ["win32"],
+
"cpu": ["ia32"]
+
},
+
"@esbuild/win32-x64@0.25.10": {
+
"integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==",
+
"os": ["win32"],
+
"cpu": ["x64"]
+
},
+
"@eslint-community/eslint-utils@4.9.0_eslint@9.36.0": {
+
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+
"dependencies": [
+
"eslint",
+
"eslint-visitor-keys@3.4.3"
+
]
+
},
+
"@eslint-community/regexpp@4.12.1": {
+
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="
+
},
+
"@eslint/config-array@0.21.0": {
+
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+
"dependencies": [
+
"@eslint/object-schema",
+
"debug",
+
"minimatch@3.1.2"
+
]
+
},
+
"@eslint/config-helpers@0.3.1": {
+
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA=="
+
},
+
"@eslint/core@0.15.2": {
+
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
+
"dependencies": [
+
"@types/json-schema"
+
]
+
},
+
"@eslint/eslintrc@3.3.1": {
+
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+
"dependencies": [
+
"ajv",
+
"debug",
+
"espree",
+
"globals@14.0.0",
+
"ignore@5.3.2",
+
"import-fresh@3.3.1",
+
"js-yaml@4.1.0",
+
"minimatch@3.1.2",
+
"strip-json-comments"
+
]
+
},
+
"@eslint/js@9.36.0": {
+
"integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw=="
+
},
+
"@eslint/object-schema@2.1.6": {
+
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="
+
},
+
"@eslint/plugin-kit@0.3.5": {
+
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
+
"dependencies": [
+
"@eslint/core",
+
"levn"
+
]
+
},
+
"@humanfs/core@0.19.1": {
+
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="
+
},
+
"@humanfs/node@0.16.7": {
+
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+
"dependencies": [
+
"@humanfs/core",
+
"@humanwhocodes/retry"
+
]
+
},
+
"@humanwhocodes/module-importer@1.0.1": {
+
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="
+
},
+
"@humanwhocodes/retry@0.4.3": {
+
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="
+
},
+
"@isaacs/fs-minipass@4.0.1": {
+
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+
"dependencies": [
+
"minipass"
+
]
+
},
+
"@jridgewell/gen-mapping@0.3.13": {
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+
"dependencies": [
+
"@jridgewell/sourcemap-codec",
+
"@jridgewell/trace-mapping"
+
]
+
},
+
"@jridgewell/remapping@2.3.5": {
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+
"dependencies": [
+
"@jridgewell/gen-mapping",
+
"@jridgewell/trace-mapping"
+
]
+
},
+
"@jridgewell/resolve-uri@3.1.2": {
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
+
},
+
"@jridgewell/sourcemap-codec@1.5.5": {
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
+
},
+
"@jridgewell/trace-mapping@0.3.31": {
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+
"dependencies": [
+
"@jridgewell/resolve-uri",
+
"@jridgewell/sourcemap-codec"
+
]
+
},
+
"@napi-rs/wasm-runtime@1.0.5": {
+
"integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==",
+
"dependencies": [
+
"@emnapi/core",
+
"@emnapi/runtime",
+
"@tybys/wasm-util"
+
]
+
},
+
"@nodelib/fs.scandir@2.1.5": {
+
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+
"dependencies": [
+
"@nodelib/fs.stat",
+
"run-parallel"
+
]
+
},
+
"@nodelib/fs.stat@2.0.5": {
+
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
+
},
+
"@nodelib/fs.walk@1.2.8": {
+
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+
"dependencies": [
+
"@nodelib/fs.scandir",
+
"fastq"
+
]
+
},
+
"@rolldown/pluginutils@1.0.0-beta.38": {
+
"integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw=="
+
},
+
"@rollup/rollup-android-arm-eabi@4.52.3": {
+
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
+
"os": ["android"],
+
"cpu": ["arm"]
+
},
+
"@rollup/rollup-android-arm64@4.52.3": {
+
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
+
"os": ["android"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-darwin-arm64@4.52.3": {
+
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
+
"os": ["darwin"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-darwin-x64@4.52.3": {
+
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
+
"os": ["darwin"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-freebsd-arm64@4.52.3": {
+
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
+
"os": ["freebsd"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-freebsd-x64@4.52.3": {
+
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
+
"os": ["freebsd"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-linux-arm-gnueabihf@4.52.3": {
+
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@rollup/rollup-linux-arm-musleabihf@4.52.3": {
+
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@rollup/rollup-linux-arm64-gnu@4.52.3": {
+
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-linux-arm64-musl@4.52.3": {
+
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-linux-loong64-gnu@4.52.3": {
+
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
+
"os": ["linux"],
+
"cpu": ["loong64"]
+
},
+
"@rollup/rollup-linux-ppc64-gnu@4.52.3": {
+
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
+
"os": ["linux"],
+
"cpu": ["ppc64"]
+
},
+
"@rollup/rollup-linux-riscv64-gnu@4.52.3": {
+
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
+
"os": ["linux"],
+
"cpu": ["riscv64"]
+
},
+
"@rollup/rollup-linux-riscv64-musl@4.52.3": {
+
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
+
"os": ["linux"],
+
"cpu": ["riscv64"]
+
},
+
"@rollup/rollup-linux-s390x-gnu@4.52.3": {
+
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
+
"os": ["linux"],
+
"cpu": ["s390x"]
+
},
+
"@rollup/rollup-linux-x64-gnu@4.52.3": {
+
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-linux-x64-musl@4.52.3": {
+
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-openharmony-arm64@4.52.3": {
+
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
+
"os": ["openharmony"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-win32-arm64-msvc@4.52.3": {
+
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
+
"os": ["win32"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-win32-ia32-msvc@4.52.3": {
+
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
+
"os": ["win32"],
+
"cpu": ["ia32"]
+
},
+
"@rollup/rollup-win32-x64-gnu@4.52.3": {
+
"integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
+
"os": ["win32"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-win32-x64-msvc@4.52.3": {
+
"integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
+
"os": ["win32"],
+
"cpu": ["x64"]
+
},
+
"@tailwindcss/node@4.1.14": {
+
"integrity": "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==",
+
"dependencies": [
+
"@jridgewell/remapping",
+
"enhanced-resolve",
+
"jiti",
+
"lightningcss",
+
"magic-string",
+
"source-map-js",
+
"tailwindcss"
+
]
+
},
+
"@tailwindcss/oxide-android-arm64@4.1.14": {
+
"integrity": "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==",
+
"os": ["android"],
+
"cpu": ["arm64"]
+
},
+
"@tailwindcss/oxide-darwin-arm64@4.1.14": {
+
"integrity": "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==",
+
"os": ["darwin"],
+
"cpu": ["arm64"]
+
},
+
"@tailwindcss/oxide-darwin-x64@4.1.14": {
+
"integrity": "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==",
+
"os": ["darwin"],
+
"cpu": ["x64"]
+
},
+
"@tailwindcss/oxide-freebsd-x64@4.1.14": {
+
"integrity": "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==",
+
"os": ["freebsd"],
+
"cpu": ["x64"]
+
},
+
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14": {
+
"integrity": "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@tailwindcss/oxide-linux-arm64-gnu@4.1.14": {
+
"integrity": "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@tailwindcss/oxide-linux-arm64-musl@4.1.14": {
+
"integrity": "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@tailwindcss/oxide-linux-x64-gnu@4.1.14": {
+
"integrity": "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@tailwindcss/oxide-linux-x64-musl@4.1.14": {
+
"integrity": "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@tailwindcss/oxide-wasm32-wasi@4.1.14": {
+
"integrity": "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==",
+
"dependencies": [
+
"@emnapi/core",
+
"@emnapi/runtime",
+
"@emnapi/wasi-threads",
+
"@napi-rs/wasm-runtime",
+
"@tybys/wasm-util",
+
"tslib"
+
],
+
"cpu": ["wasm32"]
+
},
+
"@tailwindcss/oxide-win32-arm64-msvc@4.1.14": {
+
"integrity": "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==",
+
"os": ["win32"],
+
"cpu": ["arm64"]
+
},
+
"@tailwindcss/oxide-win32-x64-msvc@4.1.14": {
+
"integrity": "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==",
+
"os": ["win32"],
+
"cpu": ["x64"]
+
},
+
"@tailwindcss/oxide@4.1.14": {
+
"integrity": "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==",
+
"dependencies": [
+
"detect-libc",
+
"tar"
+
],
+
"optionalDependencies": [
+
"@tailwindcss/oxide-android-arm64",
+
"@tailwindcss/oxide-darwin-arm64",
+
"@tailwindcss/oxide-darwin-x64",
+
"@tailwindcss/oxide-freebsd-x64",
+
"@tailwindcss/oxide-linux-arm-gnueabihf",
+
"@tailwindcss/oxide-linux-arm64-gnu",
+
"@tailwindcss/oxide-linux-arm64-musl",
+
"@tailwindcss/oxide-linux-x64-gnu",
+
"@tailwindcss/oxide-linux-x64-musl",
+
"@tailwindcss/oxide-wasm32-wasi",
+
"@tailwindcss/oxide-win32-arm64-msvc",
+
"@tailwindcss/oxide-win32-x64-msvc"
+
],
+
"scripts": true
+
},
+
"@tailwindcss/postcss@4.1.14": {
+
"integrity": "sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg==",
+
"dependencies": [
+
"@alloc/quick-lru",
+
"@tailwindcss/node",
+
"@tailwindcss/oxide",
+
"postcss",
+
"tailwindcss"
+
]
+
},
+
"@tybys/wasm-util@0.10.1": {
+
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+
"dependencies": [
+
"tslib"
+
]
+
},
+
"@types/babel__core@7.20.5": {
+
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+
"dependencies": [
+
"@babel/parser",
+
"@babel/types",
+
"@types/babel__generator",
+
"@types/babel__template",
+
"@types/babel__traverse"
+
]
+
},
+
"@types/babel__generator@7.27.0": {
+
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+
"dependencies": [
+
"@babel/types"
+
]
+
},
+
"@types/babel__template@7.4.4": {
+
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+
"dependencies": [
+
"@babel/parser",
+
"@babel/types"
+
]
+
},
+
"@types/babel__traverse@7.28.0": {
+
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+
"dependencies": [
+
"@babel/types"
+
]
+
},
+
"@types/estree@1.0.8": {
+
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
+
},
+
"@types/json-schema@7.0.15": {
+
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
+
},
+
"@types/node@24.6.2": {
+
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
+
"dependencies": [
+
"undici-types"
+
]
+
},
+
"@types/parse-json@4.0.2": {
+
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
+
},
+
"@types/react-dom@19.2.0_@types+react@19.2.0": {
+
"integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==",
+
"dependencies": [
+
"@types/react"
+
]
+
},
+
"@types/react-relay@18.2.1": {
+
"integrity": "sha512-KgmFapsxAylhxcFfaAv5GZZJhTHnDvV8IDZVsUm5afpJUvgZC1Y68ssfOGsFfiFY/2EhxHM/YPfpdKbfmF3Ecg==",
+
"dependencies": [
+
"@types/react",
+
"@types/relay-runtime"
+
]
+
},
+
"@types/react@19.2.0": {
+
"integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==",
+
"dependencies": [
+
"csstype"
+
]
+
},
+
"@types/relay-runtime@19.0.3": {
+
"integrity": "sha512-pvpWWQq5e9KeESF8klQaP2igLLhr2bRd3XxVCxNpGElsPQiP6Mejr59RT9/OGY3O3i8jAGGQsshVe0QCQDbxNg=="
+
},
+
"@typescript-eslint/eslint-plugin@8.45.0_@typescript-eslint+parser@8.45.0__eslint@9.36.0__typescript@5.9.3_eslint@9.36.0_typescript@5.9.3": {
+
"integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==",
+
"dependencies": [
+
"@eslint-community/regexpp",
+
"@typescript-eslint/parser",
+
"@typescript-eslint/scope-manager",
+
"@typescript-eslint/type-utils",
+
"@typescript-eslint/utils",
+
"@typescript-eslint/visitor-keys",
+
"eslint",
+
"graphemer",
+
"ignore@7.0.5",
+
"natural-compare",
+
"ts-api-utils",
+
"typescript"
+
]
+
},
+
"@typescript-eslint/parser@8.45.0_eslint@9.36.0_typescript@5.9.3": {
+
"integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==",
+
"dependencies": [
+
"@typescript-eslint/scope-manager",
+
"@typescript-eslint/types",
+
"@typescript-eslint/typescript-estree",
+
"@typescript-eslint/visitor-keys",
+
"debug",
+
"eslint",
+
"typescript"
+
]
+
},
+
"@typescript-eslint/project-service@8.45.0_typescript@5.9.3": {
+
"integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==",
+
"dependencies": [
+
"@typescript-eslint/tsconfig-utils",
+
"@typescript-eslint/types",
+
"debug",
+
"typescript"
+
]
+
},
+
"@typescript-eslint/scope-manager@8.45.0": {
+
"integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==",
+
"dependencies": [
+
"@typescript-eslint/types",
+
"@typescript-eslint/visitor-keys"
+
]
+
},
+
"@typescript-eslint/tsconfig-utils@8.45.0_typescript@5.9.3": {
+
"integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==",
+
"dependencies": [
+
"typescript"
+
]
+
},
+
"@typescript-eslint/type-utils@8.45.0_eslint@9.36.0_typescript@5.9.3": {
+
"integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==",
+
"dependencies": [
+
"@typescript-eslint/types",
+
"@typescript-eslint/typescript-estree",
+
"@typescript-eslint/utils",
+
"debug",
+
"eslint",
+
"ts-api-utils",
+
"typescript"
+
]
+
},
+
"@typescript-eslint/types@8.45.0": {
+
"integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA=="
+
},
+
"@typescript-eslint/typescript-estree@8.45.0_typescript@5.9.3": {
+
"integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==",
+
"dependencies": [
+
"@typescript-eslint/project-service",
+
"@typescript-eslint/tsconfig-utils",
+
"@typescript-eslint/types",
+
"@typescript-eslint/visitor-keys",
+
"debug",
+
"fast-glob",
+
"is-glob",
+
"minimatch@9.0.5",
+
"semver@7.7.2",
+
"ts-api-utils",
+
"typescript"
+
]
+
},
+
"@typescript-eslint/utils@8.45.0_eslint@9.36.0_typescript@5.9.3": {
+
"integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==",
+
"dependencies": [
+
"@eslint-community/eslint-utils",
+
"@typescript-eslint/scope-manager",
+
"@typescript-eslint/types",
+
"@typescript-eslint/typescript-estree",
+
"eslint",
+
"typescript"
+
]
+
},
+
"@typescript-eslint/visitor-keys@8.45.0": {
+
"integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==",
+
"dependencies": [
+
"@typescript-eslint/types",
+
"eslint-visitor-keys@4.2.1"
+
]
+
},
+
"@vitejs/plugin-react@5.0.4_vite@7.1.8__@types+node@24.6.2__picomatch@4.0.3_@babel+core@7.28.4_@types+node@24.6.2": {
+
"integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==",
+
"dependencies": [
+
"@babel/core",
+
"@babel/plugin-transform-react-jsx-self",
+
"@babel/plugin-transform-react-jsx-source",
+
"@rolldown/pluginutils",
+
"@types/babel__core",
+
"react-refresh",
+
"vite"
+
]
+
},
+
"acorn-jsx@5.3.2_acorn@8.15.0": {
+
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+
"dependencies": [
+
"acorn"
+
]
+
},
+
"acorn@8.15.0": {
+
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+
"bin": true
+
},
+
"ajv@6.12.6": {
+
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+
"dependencies": [
+
"fast-deep-equal",
+
"fast-json-stable-stringify",
+
"json-schema-traverse",
+
"uri-js"
+
]
+
},
+
"ansi-styles@4.3.0": {
+
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+
"dependencies": [
+
"color-convert"
+
]
+
},
+
"argparse@1.0.10": {
+
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+
"dependencies": [
+
"sprintf-js"
+
]
+
},
+
"argparse@2.0.1": {
+
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+
},
+
"asap@2.0.6": {
+
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
+
},
+
"autoprefixer@10.4.21_postcss@8.5.6": {
+
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
+
"dependencies": [
+
"browserslist",
+
"caniuse-lite",
+
"fraction.js",
+
"normalize-range",
+
"picocolors",
+
"postcss",
+
"postcss-value-parser"
+
],
+
"bin": true
+
},
+
"babel-plugin-macros@2.8.0": {
+
"integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
+
"dependencies": [
+
"@babel/runtime",
+
"cosmiconfig@6.0.0",
+
"resolve"
+
]
+
},
+
"babel-plugin-relay@20.1.1": {
+
"integrity": "sha512-BWlqLPiHbxZTxlyng2rVgsZCwztHNje7H8FR4c+UKy3ErQJBG6BKLr9vUdeR7mAZCH2v0sOAxNhG6zR1FrWjAg==",
+
"dependencies": [
+
"babel-plugin-macros",
+
"cosmiconfig@5.2.1",
+
"graphql@15.3.0"
+
]
+
},
+
"balanced-match@1.0.2": {
+
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+
},
+
"baseline-browser-mapping@2.8.10": {
+
"integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==",
+
"bin": true
+
},
+
"brace-expansion@1.1.12": {
+
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+
"dependencies": [
+
"balanced-match",
+
"concat-map"
+
]
+
},
+
"brace-expansion@2.0.2": {
+
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+
"dependencies": [
+
"balanced-match"
+
]
+
},
+
"braces@3.0.3": {
+
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+
"dependencies": [
+
"fill-range"
+
]
+
},
+
"browserslist@4.26.3": {
+
"integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
+
"dependencies": [
+
"baseline-browser-mapping",
+
"caniuse-lite",
+
"electron-to-chromium",
+
"node-releases",
+
"update-browserslist-db"
+
],
+
"bin": true
+
},
+
"caller-callsite@2.0.0": {
+
"integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==",
+
"dependencies": [
+
"callsites@2.0.0"
+
]
+
},
+
"caller-path@2.0.0": {
+
"integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==",
+
"dependencies": [
+
"caller-callsite"
+
]
+
},
+
"callsites@2.0.0": {
+
"integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ=="
+
},
+
"callsites@3.1.0": {
+
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
+
},
+
"caniuse-lite@1.0.30001746": {
+
"integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA=="
+
},
+
"chalk@4.1.2": {
+
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+
"dependencies": [
+
"ansi-styles",
+
"supports-color"
+
]
+
},
+
"chownr@3.0.0": {
+
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="
+
},
+
"color-convert@2.0.1": {
+
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+
"dependencies": [
+
"color-name"
+
]
+
},
+
"color-name@1.1.4": {
+
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+
},
+
"concat-map@0.0.1": {
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+
},
+
"convert-source-map@2.0.0": {
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
+
},
+
"cookie@1.0.2": {
+
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="
+
},
+
"cosmiconfig@5.2.1": {
+
"integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+
"dependencies": [
+
"import-fresh@2.0.0",
+
"is-directory",
+
"js-yaml@3.14.1",
+
"parse-json@4.0.0"
+
]
+
},
+
"cosmiconfig@6.0.0": {
+
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
+
"dependencies": [
+
"@types/parse-json",
+
"import-fresh@3.3.1",
+
"parse-json@5.2.0",
+
"path-type",
+
"yaml"
+
]
+
},
+
"cross-fetch@3.2.0": {
+
"integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
+
"dependencies": [
+
"node-fetch"
+
]
+
},
+
"cross-spawn@7.0.6": {
+
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+
"dependencies": [
+
"path-key",
+
"shebang-command",
+
"which"
+
]
+
},
+
"csstype@3.1.3": {
+
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+
},
+
"debug@4.4.3": {
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+
"dependencies": [
+
"ms"
+
]
+
},
+
"deep-is@0.1.4": {
+
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
+
},
+
"detect-libc@2.1.1": {
+
"integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="
+
},
+
"electron-to-chromium@1.5.229": {
+
"integrity": "sha512-cwhDcZKGcT/rEthLRJ9eBlMDkh1sorgsuk+6dpsehV0g9CABsIqBxU4rLRjG+d/U6pYU1s37A4lSKrVc5lSQYg=="
+
},
+
"enhanced-resolve@5.18.3": {
+
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+
"dependencies": [
+
"graceful-fs",
+
"tapable"
+
]
+
},
+
"error-ex@1.3.2": {
+
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+
"dependencies": [
+
"is-arrayish"
+
]
+
},
+
"esbuild@0.25.10": {
+
"integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
+
"optionalDependencies": [
+
"@esbuild/aix-ppc64",
+
"@esbuild/android-arm",
+
"@esbuild/android-arm64",
+
"@esbuild/android-x64",
+
"@esbuild/darwin-arm64",
+
"@esbuild/darwin-x64",
+
"@esbuild/freebsd-arm64",
+
"@esbuild/freebsd-x64",
+
"@esbuild/linux-arm",
+
"@esbuild/linux-arm64",
+
"@esbuild/linux-ia32",
+
"@esbuild/linux-loong64",
+
"@esbuild/linux-mips64el",
+
"@esbuild/linux-ppc64",
+
"@esbuild/linux-riscv64",
+
"@esbuild/linux-s390x",
+
"@esbuild/linux-x64",
+
"@esbuild/netbsd-arm64",
+
"@esbuild/netbsd-x64",
+
"@esbuild/openbsd-arm64",
+
"@esbuild/openbsd-x64",
+
"@esbuild/openharmony-arm64",
+
"@esbuild/sunos-x64",
+
"@esbuild/win32-arm64",
+
"@esbuild/win32-ia32",
+
"@esbuild/win32-x64"
+
],
+
"scripts": true,
+
"bin": true
+
},
+
"escalade@3.2.0": {
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
+
},
+
"escape-string-regexp@4.0.0": {
+
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+
},
+
"eslint-plugin-react-hooks@5.2.0_eslint@9.36.0": {
+
"integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+
"dependencies": [
+
"eslint"
+
]
+
},
+
"eslint-plugin-react-refresh@0.4.23_eslint@9.36.0": {
+
"integrity": "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA==",
+
"dependencies": [
+
"eslint"
+
]
+
},
+
"eslint-scope@8.4.0": {
+
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+
"dependencies": [
+
"esrecurse",
+
"estraverse"
+
]
+
},
+
"eslint-visitor-keys@3.4.3": {
+
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="
+
},
+
"eslint-visitor-keys@4.2.1": {
+
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="
+
},
+
"eslint@9.36.0": {
+
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
+
"dependencies": [
+
"@eslint-community/eslint-utils",
+
"@eslint-community/regexpp",
+
"@eslint/config-array",
+
"@eslint/config-helpers",
+
"@eslint/core",
+
"@eslint/eslintrc",
+
"@eslint/js",
+
"@eslint/plugin-kit",
+
"@humanfs/node",
+
"@humanwhocodes/module-importer",
+
"@humanwhocodes/retry",
+
"@types/estree",
+
"@types/json-schema",
+
"ajv",
+
"chalk",
+
"cross-spawn",
+
"debug",
+
"escape-string-regexp",
+
"eslint-scope",
+
"eslint-visitor-keys@4.2.1",
+
"espree",
+
"esquery",
+
"esutils",
+
"fast-deep-equal",
+
"file-entry-cache",
+
"find-up",
+
"glob-parent@6.0.2",
+
"ignore@5.3.2",
+
"imurmurhash",
+
"is-glob",
+
"json-stable-stringify-without-jsonify",
+
"lodash.merge",
+
"minimatch@3.1.2",
+
"natural-compare",
+
"optionator"
+
],
+
"bin": true
+
},
+
"espree@10.4.0_acorn@8.15.0": {
+
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+
"dependencies": [
+
"acorn",
+
"acorn-jsx",
+
"eslint-visitor-keys@4.2.1"
+
]
+
},
+
"esprima@4.0.1": {
+
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+
"bin": true
+
},
+
"esquery@1.6.0": {
+
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+
"dependencies": [
+
"estraverse"
+
]
+
},
+
"esrecurse@4.3.0": {
+
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+
"dependencies": [
+
"estraverse"
+
]
+
},
+
"estraverse@5.3.0": {
+
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
+
},
+
"esutils@2.0.3": {
+
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
+
},
+
"fast-deep-equal@3.1.3": {
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+
},
+
"fast-glob@3.3.3": {
+
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+
"dependencies": [
+
"@nodelib/fs.stat",
+
"@nodelib/fs.walk",
+
"glob-parent@5.1.2",
+
"merge2",
+
"micromatch"
+
]
+
},
+
"fast-json-stable-stringify@2.1.0": {
+
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+
},
+
"fast-levenshtein@2.0.6": {
+
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
+
},
+
"fastq@1.19.1": {
+
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+
"dependencies": [
+
"reusify"
+
]
+
},
+
"fbjs-css-vars@1.0.2": {
+
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
+
},
+
"fbjs@3.0.5": {
+
"integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==",
+
"dependencies": [
+
"cross-fetch",
+
"fbjs-css-vars",
+
"loose-envify",
+
"object-assign",
+
"promise",
+
"setimmediate",
+
"ua-parser-js"
+
]
+
},
+
"fdir@6.5.0_picomatch@4.0.3": {
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+
"dependencies": [
+
"picomatch@4.0.3"
+
],
+
"optionalPeers": [
+
"picomatch@4.0.3"
+
]
+
},
+
"file-entry-cache@8.0.0": {
+
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+
"dependencies": [
+
"flat-cache"
+
]
+
},
+
"fill-range@7.1.1": {
+
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+
"dependencies": [
+
"to-regex-range"
+
]
+
},
+
"find-up@5.0.0": {
+
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+
"dependencies": [
+
"locate-path",
+
"path-exists"
+
]
+
},
+
"flat-cache@4.0.1": {
+
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+
"dependencies": [
+
"flatted",
+
"keyv"
+
]
+
},
+
"flatted@3.3.3": {
+
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="
+
},
+
"fraction.js@4.3.7": {
+
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="
+
},
+
"fsevents@2.3.3": {
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+
"os": ["darwin"],
+
"scripts": true
+
},
+
"function-bind@1.1.2": {
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
+
},
+
"gensync@1.0.0-beta.2": {
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
+
},
+
"glob-parent@5.1.2": {
+
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+
"dependencies": [
+
"is-glob"
+
]
+
},
+
"glob-parent@6.0.2": {
+
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+
"dependencies": [
+
"is-glob"
+
]
+
},
+
"globals@14.0.0": {
+
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="
+
},
+
"globals@16.4.0": {
+
"integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="
+
},
+
"graceful-fs@4.2.11": {
+
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+
},
+
"graphemer@1.4.0": {
+
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
+
},
+
"graphql@15.3.0": {
+
"integrity": "sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w=="
+
},
+
"graphql@16.11.0": {
+
"integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw=="
+
},
+
"has-flag@4.0.0": {
+
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+
},
+
"hasown@2.0.2": {
+
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+
"dependencies": [
+
"function-bind"
+
]
+
},
+
"ignore@5.3.2": {
+
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="
+
},
+
"ignore@7.0.5": {
+
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="
+
},
+
"import-fresh@2.0.0": {
+
"integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==",
+
"dependencies": [
+
"caller-path",
+
"resolve-from@3.0.0"
+
]
+
},
+
"import-fresh@3.3.1": {
+
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+
"dependencies": [
+
"parent-module",
+
"resolve-from@4.0.0"
+
]
+
},
+
"imurmurhash@0.1.4": {
+
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
+
},
+
"invariant@2.2.4": {
+
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+
"dependencies": [
+
"loose-envify"
+
]
+
},
+
"is-arrayish@0.2.1": {
+
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
+
},
+
"is-core-module@2.16.1": {
+
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+
"dependencies": [
+
"hasown"
+
]
+
},
+
"is-directory@0.3.1": {
+
"integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw=="
+
},
+
"is-extglob@2.1.1": {
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
+
},
+
"is-glob@4.0.3": {
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+
"dependencies": [
+
"is-extglob"
+
]
+
},
+
"is-number@7.0.0": {
+
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+
},
+
"isexe@2.0.0": {
+
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+
},
+
"jiti@2.6.1": {
+
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+
"bin": true
+
},
+
"js-tokens@4.0.0": {
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+
},
+
"js-yaml@3.14.1": {
+
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+
"dependencies": [
+
"argparse@1.0.10",
+
"esprima"
+
],
+
"bin": true
+
},
+
"js-yaml@4.1.0": {
+
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+
"dependencies": [
+
"argparse@2.0.1"
+
],
+
"bin": true
+
},
+
"jsesc@3.1.0": {
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+
"bin": true
+
},
+
"json-buffer@3.0.1": {
+
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
+
},
+
"json-parse-better-errors@1.0.2": {
+
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
+
},
+
"json-parse-even-better-errors@2.3.1": {
+
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
+
},
+
"json-schema-traverse@0.4.1": {
+
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+
},
+
"json-stable-stringify-without-jsonify@1.0.1": {
+
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
+
},
+
"json5@2.2.3": {
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+
"bin": true
+
},
+
"keyv@4.5.4": {
+
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+
"dependencies": [
+
"json-buffer"
+
]
+
},
+
"levn@0.4.1": {
+
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+
"dependencies": [
+
"prelude-ls",
+
"type-check"
+
]
+
},
+
"lightningcss-darwin-arm64@1.30.1": {
+
"integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
+
"os": ["darwin"],
+
"cpu": ["arm64"]
+
},
+
"lightningcss-darwin-x64@1.30.1": {
+
"integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
+
"os": ["darwin"],
+
"cpu": ["x64"]
+
},
+
"lightningcss-freebsd-x64@1.30.1": {
+
"integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
+
"os": ["freebsd"],
+
"cpu": ["x64"]
+
},
+
"lightningcss-linux-arm-gnueabihf@1.30.1": {
+
"integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"lightningcss-linux-arm64-gnu@1.30.1": {
+
"integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"lightningcss-linux-arm64-musl@1.30.1": {
+
"integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"lightningcss-linux-x64-gnu@1.30.1": {
+
"integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"lightningcss-linux-x64-musl@1.30.1": {
+
"integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"lightningcss-win32-arm64-msvc@1.30.1": {
+
"integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
+
"os": ["win32"],
+
"cpu": ["arm64"]
+
},
+
"lightningcss-win32-x64-msvc@1.30.1": {
+
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
+
"os": ["win32"],
+
"cpu": ["x64"]
+
},
+
"lightningcss@1.30.1": {
+
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
+
"dependencies": [
+
"detect-libc"
+
],
+
"optionalDependencies": [
+
"lightningcss-darwin-arm64",
+
"lightningcss-darwin-x64",
+
"lightningcss-freebsd-x64",
+
"lightningcss-linux-arm-gnueabihf",
+
"lightningcss-linux-arm64-gnu",
+
"lightningcss-linux-arm64-musl",
+
"lightningcss-linux-x64-gnu",
+
"lightningcss-linux-x64-musl",
+
"lightningcss-win32-arm64-msvc",
+
"lightningcss-win32-x64-msvc"
+
]
+
},
+
"lines-and-columns@1.2.4": {
+
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
+
},
+
"locate-path@6.0.0": {
+
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+
"dependencies": [
+
"p-locate"
+
]
+
},
+
"lodash.merge@4.6.2": {
+
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+
},
+
"loose-envify@1.4.0": {
+
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+
"dependencies": [
+
"js-tokens"
+
],
+
"bin": true
+
},
+
"lru-cache@5.1.1": {
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+
"dependencies": [
+
"yallist@3.1.1"
+
]
+
},
+
"magic-string@0.30.19": {
+
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+
"dependencies": [
+
"@jridgewell/sourcemap-codec"
+
]
+
},
+
"merge2@1.4.1": {
+
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
+
},
+
"micromatch@4.0.8": {
+
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+
"dependencies": [
+
"braces",
+
"picomatch@2.3.1"
+
]
+
},
+
"minimatch@3.1.2": {
+
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+
"dependencies": [
+
"brace-expansion@1.1.12"
+
]
+
},
+
"minimatch@9.0.5": {
+
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+
"dependencies": [
+
"brace-expansion@2.0.2"
+
]
+
},
+
"minipass@7.1.2": {
+
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
+
},
+
"minizlib@3.1.0": {
+
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+
"dependencies": [
+
"minipass"
+
]
+
},
+
"ms@2.1.3": {
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+
},
+
"nanoid@3.3.11": {
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+
"bin": true
+
},
+
"natural-compare@1.4.0": {
+
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
+
},
+
"node-fetch@2.7.0": {
+
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+
"dependencies": [
+
"whatwg-url"
+
]
+
},
+
"node-releases@2.0.21": {
+
"integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw=="
+
},
+
"normalize-range@0.1.2": {
+
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="
+
},
+
"nullthrows@1.1.1": {
+
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="
+
},
+
"object-assign@4.1.1": {
+
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
+
},
+
"optionator@0.9.4": {
+
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+
"dependencies": [
+
"deep-is",
+
"fast-levenshtein",
+
"levn",
+
"prelude-ls",
+
"type-check",
+
"word-wrap"
+
]
+
},
+
"p-limit@3.1.0": {
+
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+
"dependencies": [
+
"yocto-queue"
+
]
+
},
+
"p-locate@5.0.0": {
+
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+
"dependencies": [
+
"p-limit"
+
]
+
},
+
"parent-module@1.0.1": {
+
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+
"dependencies": [
+
"callsites@3.1.0"
+
]
+
},
+
"parse-json@4.0.0": {
+
"integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
+
"dependencies": [
+
"error-ex",
+
"json-parse-better-errors"
+
]
+
},
+
"parse-json@5.2.0": {
+
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+
"dependencies": [
+
"@babel/code-frame",
+
"error-ex",
+
"json-parse-even-better-errors",
+
"lines-and-columns"
+
]
+
},
+
"path-exists@4.0.0": {
+
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+
},
+
"path-key@3.1.1": {
+
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
+
},
+
"path-parse@1.0.7": {
+
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+
},
+
"path-type@4.0.0": {
+
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
+
},
+
"picocolors@1.1.1": {
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+
},
+
"picomatch@2.3.1": {
+
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
+
},
+
"picomatch@4.0.3": {
+
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
+
},
+
"postcss-value-parser@4.2.0": {
+
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+
},
+
"postcss@8.5.6": {
+
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+
"dependencies": [
+
"nanoid",
+
"picocolors",
+
"source-map-js"
+
]
+
},
+
"prelude-ls@1.2.1": {
+
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
+
},
+
"promise@7.3.1": {
+
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+
"dependencies": [
+
"asap"
+
]
+
},
+
"punycode@2.3.1": {
+
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
+
},
+
"queue-microtask@1.2.3": {
+
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
+
},
+
"react-dom@19.2.0_react@19.2.0": {
+
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
+
"dependencies": [
+
"react",
+
"scheduler"
+
]
+
},
+
"react-refresh@0.17.0": {
+
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="
+
},
+
"react-relay@20.1.1_react@19.2.0": {
+
"integrity": "sha512-pwl7wHHXCOx32dOg4TVNkhVojgGVvOBMo9pcMGeM1BIFYvmwGauKTtBB+qr5buXHGooCL8TXjMVg+ZLizIsbeQ==",
+
"dependencies": [
+
"@babel/runtime",
+
"fbjs",
+
"invariant",
+
"nullthrows",
+
"react",
+
"relay-runtime"
+
]
+
},
+
"react-router-dom@7.9.3_react@19.2.0_react-dom@19.2.0__react@19.2.0": {
+
"integrity": "sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==",
+
"dependencies": [
+
"react",
+
"react-dom",
+
"react-router"
+
]
+
},
+
"react-router@7.9.3_react@19.2.0_react-dom@19.2.0__react@19.2.0": {
+
"integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==",
+
"dependencies": [
+
"cookie",
+
"react",
+
"react-dom",
+
"set-cookie-parser"
+
],
+
"optionalPeers": [
+
"react-dom"
+
]
+
},
+
"react@19.2.0": {
+
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="
+
},
+
"relay-compiler@20.1.1": {
+
"integrity": "sha512-J/FFFLS/3vnbDkyQMw8l3Ev7dNHXMgC1RAs0fITz4Q63TFPOw142knKxY1Mm5ZZBABkAs9g2JghXaqM+phwTxw==",
+
"bin": true
+
},
+
"relay-runtime@20.1.1": {
+
"integrity": "sha512-N98ZkkyuIHdXmHaPuljihM1QbFYXATF0gxA0CESFphszsQzXs9A/zZloVfzdOI/xg3B3CfM/5nozvIoeTDIcfw==",
+
"dependencies": [
+
"@babel/runtime",
+
"fbjs",
+
"invariant"
+
]
+
},
+
"resolve-from@3.0.0": {
+
"integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw=="
+
},
+
"resolve-from@4.0.0": {
+
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
+
},
+
"resolve@1.22.10": {
+
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+
"dependencies": [
+
"is-core-module",
+
"path-parse",
+
"supports-preserve-symlinks-flag"
+
],
+
"bin": true
+
},
+
"reusify@1.1.0": {
+
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
+
},
+
"rollup@4.52.3": {
+
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
+
"dependencies": [
+
"@types/estree"
+
],
+
"optionalDependencies": [
+
"@rollup/rollup-android-arm-eabi",
+
"@rollup/rollup-android-arm64",
+
"@rollup/rollup-darwin-arm64",
+
"@rollup/rollup-darwin-x64",
+
"@rollup/rollup-freebsd-arm64",
+
"@rollup/rollup-freebsd-x64",
+
"@rollup/rollup-linux-arm-gnueabihf",
+
"@rollup/rollup-linux-arm-musleabihf",
+
"@rollup/rollup-linux-arm64-gnu",
+
"@rollup/rollup-linux-arm64-musl",
+
"@rollup/rollup-linux-loong64-gnu",
+
"@rollup/rollup-linux-ppc64-gnu",
+
"@rollup/rollup-linux-riscv64-gnu",
+
"@rollup/rollup-linux-riscv64-musl",
+
"@rollup/rollup-linux-s390x-gnu",
+
"@rollup/rollup-linux-x64-gnu",
+
"@rollup/rollup-linux-x64-musl",
+
"@rollup/rollup-openharmony-arm64",
+
"@rollup/rollup-win32-arm64-msvc",
+
"@rollup/rollup-win32-ia32-msvc",
+
"@rollup/rollup-win32-x64-gnu",
+
"@rollup/rollup-win32-x64-msvc",
+
"fsevents"
+
],
+
"bin": true
+
},
+
"run-parallel@1.2.0": {
+
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+
"dependencies": [
+
"queue-microtask"
+
]
+
},
+
"scheduler@0.27.0": {
+
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="
+
},
+
"semver@6.3.1": {
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+
"bin": true
+
},
+
"semver@7.7.2": {
+
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+
"bin": true
+
},
+
"set-cookie-parser@2.7.1": {
+
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
+
},
+
"setimmediate@1.0.5": {
+
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+
},
+
"shebang-command@2.0.0": {
+
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+
"dependencies": [
+
"shebang-regex"
+
]
+
},
+
"shebang-regex@3.0.0": {
+
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
+
},
+
"source-map-js@1.2.1": {
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
+
},
+
"sprintf-js@1.0.3": {
+
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+
},
+
"strip-json-comments@3.1.1": {
+
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
+
},
+
"supports-color@7.2.0": {
+
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+
"dependencies": [
+
"has-flag"
+
]
+
},
+
"supports-preserve-symlinks-flag@1.0.0": {
+
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
+
},
+
"tailwindcss@4.1.14": {
+
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA=="
+
},
+
"tapable@2.2.3": {
+
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg=="
+
},
+
"tar@7.5.1": {
+
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
+
"dependencies": [
+
"@isaacs/fs-minipass",
+
"chownr",
+
"minipass",
+
"minizlib",
+
"yallist@5.0.0"
+
]
+
},
+
"tinyglobby@0.2.15_picomatch@4.0.3": {
+
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+
"dependencies": [
+
"fdir",
+
"picomatch@4.0.3"
+
]
+
},
+
"to-regex-range@5.0.1": {
+
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+
"dependencies": [
+
"is-number"
+
]
+
},
+
"tr46@0.0.3": {
+
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+
},
+
"ts-api-utils@2.1.0_typescript@5.9.3": {
+
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+
"dependencies": [
+
"typescript"
+
]
+
},
+
"tslib@2.8.1": {
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+
},
+
"type-check@0.4.0": {
+
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+
"dependencies": [
+
"prelude-ls"
+
]
+
},
+
"typescript-eslint@8.45.0_eslint@9.36.0_typescript@5.9.3_@typescript-eslint+parser@8.45.0__eslint@9.36.0__typescript@5.9.3": {
+
"integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==",
+
"dependencies": [
+
"@typescript-eslint/eslint-plugin",
+
"@typescript-eslint/parser",
+
"@typescript-eslint/typescript-estree",
+
"@typescript-eslint/utils",
+
"eslint",
+
"typescript"
+
]
+
},
+
"typescript@5.9.3": {
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+
"bin": true
+
},
+
"ua-parser-js@1.0.41": {
+
"integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==",
+
"bin": true
+
},
+
"undici-types@7.13.0": {
+
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="
+
},
+
"update-browserslist-db@1.1.3_browserslist@4.26.3": {
+
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+
"dependencies": [
+
"browserslist",
+
"escalade",
+
"picocolors"
+
],
+
"bin": true
+
},
+
"uri-js@4.4.1": {
+
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+
"dependencies": [
+
"punycode"
+
]
+
},
+
"vite@7.1.8_@types+node@24.6.2_picomatch@4.0.3": {
+
"integrity": "sha512-oBXvfSHEOL8jF+R9Am7h59Up07kVVGH1NrFGFoEG6bPDZP3tGpQhvkBpy5x7U6+E6wZCu9OihsWgJqDbQIm8LQ==",
+
"dependencies": [
+
"@types/node",
+
"esbuild",
+
"fdir",
+
"picomatch@4.0.3",
+
"postcss",
+
"rollup",
+
"tinyglobby"
+
],
+
"optionalDependencies": [
+
"fsevents"
+
],
+
"optionalPeers": [
+
"@types/node"
+
],
+
"bin": true
+
},
+
"webidl-conversions@3.0.1": {
+
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+
},
+
"whatwg-url@5.0.0": {
+
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+
"dependencies": [
+
"tr46",
+
"webidl-conversions"
+
]
+
},
+
"which@2.0.2": {
+
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+
"dependencies": [
+
"isexe"
+
],
+
"bin": true
+
},
+
"word-wrap@1.2.5": {
+
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
+
},
+
"yallist@3.1.1": {
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+
},
+
"yallist@5.0.0": {
+
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="
+
},
+
"yaml@1.10.2": {
+
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
+
},
+
"yocto-queue@0.1.0": {
+
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
+
}
+
},
+
"workspace": {
+
"packageJson": {
+
"dependencies": [
+
"npm:@eslint/js@^9.36.0",
+
"npm:@tailwindcss/postcss@^4.1.14",
+
"npm:@types/node@^24.6.0",
+
"npm:@types/react-dom@^19.1.9",
+
"npm:@types/react-relay@^18.2.1",
+
"npm:@types/react@^19.1.16",
+
"npm:@types/relay-runtime@^19.0.3",
+
"npm:@vitejs/plugin-react@^5.0.4",
+
"npm:autoprefixer@^10.4.21",
+
"npm:babel-plugin-relay@^20.1.1",
+
"npm:eslint-plugin-react-hooks@^5.2.0",
+
"npm:eslint-plugin-react-refresh@~0.4.22",
+
"npm:eslint@^9.36.0",
+
"npm:globals@^16.4.0",
+
"npm:graphql@^16.11.0",
+
"npm:postcss@^8.5.6",
+
"npm:react-dom@^19.1.1",
+
"npm:react-relay@^20.1.1",
+
"npm:react-router-dom@^7.9.3",
+
"npm:react@^19.1.1",
+
"npm:relay-compiler@^20.1.1",
+
"npm:relay-runtime@^20.1.1",
+
"npm:tailwindcss@^4.1.14",
+
"npm:typescript-eslint@^8.45.0",
+
"npm:typescript@~5.9.3",
+
"npm:vite@^7.1.7"
+
]
+
}
+
}
+
}
+23
eslint.config.js
···
+
import js from '@eslint/js'
+
import globals from 'globals'
+
import reactHooks from 'eslint-plugin-react-hooks'
+
import reactRefresh from 'eslint-plugin-react-refresh'
+
import tseslint from 'typescript-eslint'
+
import { defineConfig, globalIgnores } from 'eslint/config'
+
+
export default defineConfig([
+
globalIgnores(['dist']),
+
{
+
files: ['**/*.{ts,tsx}'],
+
extends: [
+
js.configs.recommended,
+
tseslint.configs.recommended,
+
reactHooks.configs['recommended-latest'],
+
reactRefresh.configs.vite,
+
],
+
languageOptions: {
+
ecmaVersion: 2020,
+
globals: globals.browser,
+
},
+
},
+
])
+19
index.html
···
+
<!doctype html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8" />
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
<title>slices-relay</title>
+
<style>
+
body {
+
background-color: #09090b;
+
margin: 0;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="root"></div>
+
<script type="module" src="/src/main.tsx"></script>
+
</body>
+
</html>
+47
package.json
···
+
{
+
"name": "slices-relay",
+
"private": true,
+
"version": "0.0.0",
+
"type": "module",
+
"scripts": {
+
"dev": "vite",
+
"build": "tsc -b && vite build",
+
"lint": "eslint .",
+
"preview": "vite preview",
+
"schema:dev": "npx get-graphql-schema 'http://localhost:3000/graphql?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a' > schema.graphql",
+
"schema:prod": "npx get-graphql-schema 'https://api.slices.network/graphql?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a' > schema.graphql"
+
},
+
"dependencies": {
+
"react": "^19.1.1",
+
"react-dom": "^19.1.1",
+
"react-relay": "^20.1.1",
+
"react-router-dom": "^7.9.3",
+
"relay-runtime": "^20.1.1"
+
},
+
"devDependencies": {
+
"@eslint/js": "^9.36.0",
+
"@tailwindcss/postcss": "^4.1.14",
+
"@types/node": "^24.6.0",
+
"@types/react": "^19.1.16",
+
"@types/react-dom": "^19.1.9",
+
"@types/react-relay": "^18.2.1",
+
"@types/relay-runtime": "^19.0.3",
+
"@vitejs/plugin-react": "^5.0.4",
+
"autoprefixer": "^10.4.21",
+
"babel-plugin-relay": "^20.1.1",
+
"eslint": "^9.36.0",
+
"eslint-plugin-react-hooks": "^5.2.0",
+
"eslint-plugin-react-refresh": "^0.4.22",
+
"globals": "^16.4.0",
+
"graphql": "^16.11.0",
+
"postcss": "^8.5.6",
+
"relay-compiler": "^20.1.1",
+
"tailwindcss": "^4.1.14",
+
"typescript": "~5.9.3",
+
"typescript-eslint": "^8.45.0",
+
"vite": "^7.1.7"
+
},
+
"overrides": {
+
"graphql": "^16.11.0"
+
}
+
}
+6
postcss.config.js
···
+
export default {
+
plugins: {
+
'@tailwindcss/postcss': {},
+
autoprefixer: {},
+
},
+
}
+5
relay.config.json
···
+
{
+
"src": "./src",
+
"schema": "./schema.graphql",
+
"language": "typescript"
+
}
+586
schema.graphql
···
+
"""
+
Indicates that an Input Object is a OneOf Input Object (and thus requires exactly one of its field be provided)
+
"""
+
directive @oneOf on INPUT_OBJECT
+
+
"""
+
Provides a scalar specification URL for specifying the behavior of custom scalar types.
+
"""
+
directive @specifiedBy(
+
"""URL that specifies the behavior of this scalar."""
+
url: String!
+
) on SCALAR
+
+
input AggregationOrderBy {
+
count: SortDirection
+
}
+
+
type AppBskyActorProfile {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
avatar: Blob
+
banner: Blob
+
createdAt: String
+
description: String
+
displayName: String
+
joinedViaStarterPack: JSON
+
labels: JSON
+
pinnedPost: JSON
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyActorProfileAggregated {
+
avatar: String
+
banner: String
+
createdAt: String
+
description: String
+
displayName: String
+
joinedViaStarterPack: String
+
labels: String
+
pinnedPost: String
+
count: Int!
+
}
+
+
type AppBskyActorProfileConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyActorProfileEdge!]!
+
nodes: [AppBskyActorProfile!]!
+
}
+
+
type AppBskyActorProfileEdge {
+
node: AppBskyActorProfile!
+
cursor: String!
+
}
+
+
type AppBskyEmbedExternal {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
external: JSON!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyEmbedExternalAggregated {
+
external: String
+
count: Int!
+
}
+
+
type AppBskyEmbedExternalConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyEmbedExternalEdge!]!
+
nodes: [AppBskyEmbedExternal!]!
+
}
+
+
type AppBskyEmbedExternalEdge {
+
node: AppBskyEmbedExternal!
+
cursor: String!
+
}
+
+
type AppBskyEmbedImages {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
images: JSON!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyEmbedImagesAggregated {
+
images: String
+
count: Int!
+
}
+
+
type AppBskyEmbedImagesConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyEmbedImagesEdge!]!
+
nodes: [AppBskyEmbedImages!]!
+
}
+
+
type AppBskyEmbedImagesEdge {
+
node: AppBskyEmbedImages!
+
cursor: String!
+
}
+
+
type AppBskyEmbedRecord {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
record: JSON!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyEmbedRecordAggregated {
+
record: String
+
count: Int!
+
}
+
+
type AppBskyEmbedRecordConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyEmbedRecordEdge!]!
+
nodes: [AppBskyEmbedRecord!]!
+
}
+
+
type AppBskyEmbedRecordEdge {
+
node: AppBskyEmbedRecord!
+
cursor: String!
+
}
+
+
type AppBskyEmbedRecordWithMedia {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
media: JSON!
+
record: JSON!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyEmbedRecordWithMediaAggregated {
+
media: String
+
record: String
+
count: Int!
+
}
+
+
type AppBskyEmbedRecordWithMediaConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyEmbedRecordWithMediaEdge!]!
+
nodes: [AppBskyEmbedRecordWithMedia!]!
+
}
+
+
type AppBskyEmbedRecordWithMediaEdge {
+
node: AppBskyEmbedRecordWithMedia!
+
cursor: String!
+
}
+
+
type AppBskyEmbedVideo {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
alt: String
+
aspectRatio: JSON
+
captions: JSON
+
video: Blob!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyEmbedVideoAggregated {
+
alt: String
+
aspectRatio: String
+
captions: String
+
video: String
+
count: Int!
+
}
+
+
type AppBskyEmbedVideoConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyEmbedVideoEdge!]!
+
nodes: [AppBskyEmbedVideo!]!
+
}
+
+
type AppBskyEmbedVideoEdge {
+
node: AppBskyEmbedVideo!
+
cursor: String!
+
}
+
+
type AppBskyFeedPostgate {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
createdAt: String!
+
detachedEmbeddingUris: [String]
+
embeddingRules: JSON
+
post: String!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyFeedPostgateAggregated {
+
createdAt: String
+
detachedEmbeddingUris: String
+
embeddingRules: String
+
post: String
+
count: Int!
+
}
+
+
type AppBskyFeedPostgateConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyFeedPostgateEdge!]!
+
nodes: [AppBskyFeedPostgate!]!
+
}
+
+
type AppBskyFeedPostgateEdge {
+
node: AppBskyFeedPostgate!
+
cursor: String!
+
}
+
+
type AppBskyFeedThreadgate {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
allow: JSON
+
createdAt: String!
+
hiddenReplies: [String]
+
post: String!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyFeedThreadgateAggregated {
+
allow: String
+
createdAt: String
+
hiddenReplies: String
+
post: String
+
count: Int!
+
}
+
+
type AppBskyFeedThreadgateConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyFeedThreadgateEdge!]!
+
nodes: [AppBskyFeedThreadgate!]!
+
}
+
+
type AppBskyFeedThreadgateEdge {
+
node: AppBskyFeedThreadgate!
+
cursor: String!
+
}
+
+
type AppBskyRichtextFacet {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
features: JSON!
+
index: JSON!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type AppBskyRichtextFacetAggregated {
+
features: String
+
index: String
+
count: Int!
+
}
+
+
type AppBskyRichtextFacetConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [AppBskyRichtextFacetEdge!]!
+
nodes: [AppBskyRichtextFacet!]!
+
}
+
+
type AppBskyRichtextFacetEdge {
+
node: AppBskyRichtextFacet!
+
cursor: String!
+
}
+
+
type Blob {
+
ref: String!
+
mimeType: String!
+
size: Int!
+
+
"""
+
Generate CDN URL for the blob with the specified preset (avatar, banner, feed_thumbnail, feed_fullsize)
+
"""
+
url(preset: String): String!
+
}
+
+
type ComAtprotoRepoStrongRef {
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
cid: String!
+
uri: String!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type ComAtprotoRepoStrongRefAggregated {
+
cid: String
+
uri: String
+
count: Int!
+
}
+
+
type ComAtprotoRepoStrongRefConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [ComAtprotoRepoStrongRefEdge!]!
+
nodes: [ComAtprotoRepoStrongRef!]!
+
}
+
+
type ComAtprotoRepoStrongRefEdge {
+
node: ComAtprotoRepoStrongRef!
+
cursor: String!
+
}
+
+
type FmTealAlphaFeedPlay {
+
uri: String!
+
cid: String!
+
did: String!
+
indexedAt: String!
+
actorHandle: String
+
artistMbIds: [String]
+
artistNames: [String]
+
artists: JSON
+
duration: Int
+
isrc: String
+
musicServiceBaseDomain: String
+
originUrl: String
+
playedTime: String
+
recordingMbId: String
+
releaseMbId: String
+
releaseName: String
+
submissionClientAgent: String
+
trackMbId: String
+
trackName: String!
+
appBskyFeedPostgate(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgate(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfile: AppBskyActorProfile
+
fmTealAlphaFeedPlay(limit: Int): [FmTealAlphaFeedPlay!]!
+
appBskyFeedPostgates(limit: Int): [AppBskyFeedPostgate!]!
+
appBskyFeedThreadgates(limit: Int): [AppBskyFeedThreadgate!]!
+
appBskyActorProfiles(limit: Int): [AppBskyActorProfile!]!
+
fmTealAlphaFeedPlays(limit: Int): [FmTealAlphaFeedPlay!]!
+
}
+
+
type FmTealAlphaFeedPlayAggregated {
+
artistMbIds: String
+
artistNames: String
+
artists: String
+
duration: String
+
isrc: String
+
musicServiceBaseDomain: String
+
originUrl: String
+
playedTime: String
+
recordingMbId: String
+
releaseMbId: String
+
releaseName: String
+
submissionClientAgent: String
+
trackMbId: String
+
trackName: String
+
count: Int!
+
}
+
+
type FmTealAlphaFeedPlayConnection {
+
totalCount: Int!
+
pageInfo: PageInfo!
+
edges: [FmTealAlphaFeedPlayEdge!]!
+
nodes: [FmTealAlphaFeedPlay!]!
+
}
+
+
type FmTealAlphaFeedPlayEdge {
+
node: FmTealAlphaFeedPlay!
+
cursor: String!
+
}
+
+
scalar JSON
+
+
type Mutation {
+
"""Sync user collections for a given DID"""
+
syncUserCollections(did: String!): SyncResult!
+
}
+
+
type PageInfo {
+
hasNextPage: Boolean!
+
hasPreviousPage: Boolean!
+
startCursor: String
+
endCursor: String
+
}
+
+
type Query {
+
"""Query app.bsky.embed.record records"""
+
appBskyEmbedRecords(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedRecordConnection!
+
+
"""
+
Aggregated query for app.bsky.embed.record records with GROUP BY support
+
"""
+
appBskyEmbedRecordsAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedRecordAggregated!]!
+
+
"""Query app.bsky.embed.images records"""
+
appBskyEmbedImageses(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedImagesConnection!
+
+
"""
+
Aggregated query for app.bsky.embed.images records with GROUP BY support
+
"""
+
appBskyEmbedImagesesAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedImagesAggregated!]!
+
+
"""Query app.bsky.embed.video records"""
+
appBskyEmbedVideos(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedVideoConnection!
+
+
"""
+
Aggregated query for app.bsky.embed.video records with GROUP BY support
+
"""
+
appBskyEmbedVideosAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedVideoAggregated!]!
+
+
"""Query app.bsky.embed.recordWithMedia records"""
+
appBskyEmbedRecordWithMedias(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedRecordWithMediaConnection!
+
+
"""
+
Aggregated query for app.bsky.embed.recordWithMedia records with GROUP BY support
+
"""
+
appBskyEmbedRecordWithMediasAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedRecordWithMediaAggregated!]!
+
+
"""Query app.bsky.embed.external records"""
+
appBskyEmbedExternals(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyEmbedExternalConnection!
+
+
"""
+
Aggregated query for app.bsky.embed.external records with GROUP BY support
+
"""
+
appBskyEmbedExternalsAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyEmbedExternalAggregated!]!
+
+
"""Query app.bsky.feed.postgate records"""
+
appBskyFeedPostgates(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyFeedPostgateConnection!
+
+
"""
+
Aggregated query for app.bsky.feed.postgate records with GROUP BY support
+
"""
+
appBskyFeedPostgatesAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyFeedPostgateAggregated!]!
+
+
"""Query app.bsky.feed.threadgate records"""
+
appBskyFeedThreadgates(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyFeedThreadgateConnection!
+
+
"""
+
Aggregated query for app.bsky.feed.threadgate records with GROUP BY support
+
"""
+
appBskyFeedThreadgatesAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyFeedThreadgateAggregated!]!
+
+
"""Query app.bsky.richtext.facet records"""
+
appBskyRichtextFacets(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyRichtextFacetConnection!
+
+
"""
+
Aggregated query for app.bsky.richtext.facet records with GROUP BY support
+
"""
+
appBskyRichtextFacetsAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyRichtextFacetAggregated!]!
+
+
"""Query app.bsky.actor.profile records"""
+
appBskyActorProfiles(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): AppBskyActorProfileConnection!
+
+
"""
+
Aggregated query for app.bsky.actor.profile records with GROUP BY support
+
"""
+
appBskyActorProfilesAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [AppBskyActorProfileAggregated!]!
+
+
"""Query com.atproto.repo.strongRef records"""
+
comAtprotoRepoStrongRefs(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): ComAtprotoRepoStrongRefConnection!
+
+
"""
+
Aggregated query for com.atproto.repo.strongRef records with GROUP BY support
+
"""
+
comAtprotoRepoStrongRefsAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [ComAtprotoRepoStrongRefAggregated!]!
+
+
"""Query fm.teal.alpha.feed.play records"""
+
fmTealAlphaFeedPlays(first: Int, after: String, last: Int, before: String, sortBy: [SortField], where: JSON): FmTealAlphaFeedPlayConnection!
+
+
"""
+
Aggregated query for fm.teal.alpha.feed.play records with GROUP BY support
+
"""
+
fmTealAlphaFeedPlaysAggregated(groupBy: [String!]!, where: JSON, orderBy: AggregationOrderBy, limit: Int): [FmTealAlphaFeedPlayAggregated!]!
+
}
+
+
enum SortDirection {
+
asc
+
desc
+
}
+
+
input SortField {
+
field: String!
+
direction: SortDirection!
+
}
+
+
type SyncResult {
+
success: Boolean!
+
reposProcessed: Int!
+
recordsSynced: Int!
+
timedOut: Boolean!
+
message: String!
+
}
+
+94
src/AlbumItem.tsx
···
+
import { useAlbumArt } from "./useAlbumArt";
+
+
interface Artist {
+
artistName: string;
+
}
+
+
interface AlbumItemProps {
+
releaseName: string;
+
releaseMbId: string | null | undefined;
+
artists: string | null | undefined;
+
count: number;
+
rank: number;
+
maxCount: number;
+
}
+
+
export default function AlbumItem({
+
releaseName,
+
releaseMbId,
+
artists,
+
count,
+
rank,
+
maxCount,
+
}: AlbumItemProps) {
+
const { albumArtUrl, isLoading } = useAlbumArt(releaseMbId);
+
const barWidth = maxCount > 0 ? (count / maxCount) * 100 : 0;
+
+
// Parse artists JSON
+
let artistNames = "Unknown Artist";
+
if (artists) {
+
try {
+
const parsed = typeof artists === 'string' ? JSON.parse(artists) : artists;
+
if (Array.isArray(parsed)) {
+
artistNames = parsed.map((a: Artist) => a.artistName).join(", ");
+
} else if (typeof parsed === 'string') {
+
artistNames = parsed;
+
}
+
} catch (e) {
+
console.log('Failed to parse artists:', artists, e);
+
artistNames = String(artists);
+
}
+
} else {
+
console.log('No artists data for:', releaseName);
+
}
+
+
return (
+
<div className="group py-3 px-4 hover:bg-zinc-900/50 transition-colors relative overflow-hidden">
+
<div
+
className="absolute inset-y-0 left-0 bg-violet-500/10 transition-all"
+
style={{ width: `${barWidth}%` }}
+
/>
+
<div className="flex items-center gap-4 relative">
+
<div className="text-xs text-zinc-600 w-8 text-right flex-shrink-0 font-medium">
+
{rank}
+
</div>
+
+
<div className="flex-shrink-0">
+
{isLoading ? (
+
<div className="w-10 h-10 bg-zinc-800 animate-pulse" />
+
) : albumArtUrl ? (
+
<img
+
src={albumArtUrl}
+
alt={`${releaseName} album art`}
+
className="w-10 h-10 object-cover"
+
loading="lazy"
+
/>
+
) : (
+
<div className="w-10 h-10 bg-zinc-800 flex items-center justify-center">
+
<svg
+
className="w-5 h-5 text-zinc-600"
+
fill="currentColor"
+
viewBox="0 0 20 20"
+
>
+
<path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" />
+
</svg>
+
</div>
+
)}
+
</div>
+
+
<div className="flex-1 min-w-0">
+
<h3 className="text-sm font-medium text-zinc-100 truncate">
+
{releaseName}
+
</h3>
+
<p className="text-xs text-zinc-500 truncate">{artistNames}</p>
+
</div>
+
+
<div className="text-right flex-shrink-0">
+
<p className="text-xs text-zinc-400 font-medium">
+
{count.toLocaleString()}
+
</p>
+
</div>
+
</div>
+
</div>
+
);
+
}
+183
src/App.tsx
···
+
import { graphql, useLazyLoadQuery, usePaginationFragment } from "react-relay";
+
import { useEffect, useRef, useState } from "react";
+
import type { AppQuery } from "./__generated__/AppQuery.graphql";
+
import type { App_plays$key } from "./__generated__/App_plays.graphql";
+
import TrackItem from "./TrackItem";
+
import TopAlbums from "./TopAlbums";
+
import TopTracks from "./TopTracks";
+
+
export default function App() {
+
const [activeTab, setActiveTab] = useState<"recent" | "tracks" | "albums">("recent");
+
const queryData = useLazyLoadQuery<AppQuery>(
+
graphql`
+
query AppQuery {
+
...App_plays
+
}
+
`,
+
{}
+
);
+
+
const { data, loadNext, hasNext, isLoadingNext } = usePaginationFragment<
+
AppQuery,
+
App_plays$key
+
>(
+
graphql`
+
fragment App_plays on Query
+
@refetchable(queryName: "AppPaginationQuery")
+
@argumentDefinitions(
+
cursor: { type: "String" }
+
count: { type: "Int", defaultValue: 20 }
+
) {
+
fmTealAlphaFeedPlays(
+
first: $count
+
after: $cursor
+
sortBy: [{ field: "playedTime", direction: desc }]
+
) @connection(key: "App_fmTealAlphaFeedPlays", filters: ["sortBy"]) {
+
totalCount
+
edges {
+
node {
+
playedTime
+
...TrackItem_play
+
}
+
}
+
}
+
}
+
`,
+
queryData
+
);
+
+
const loadMoreRef = useRef<HTMLDivElement>(null);
+
+
useEffect(() => {
+
window.scrollTo(0, 0);
+
}, []);
+
+
const plays =
+
data?.fmTealAlphaFeedPlays?.edges
+
?.map((edge) => edge.node)
+
.filter((n) => n != null) || [];
+
+
useEffect(() => {
+
if (!loadMoreRef.current || !hasNext || activeTab !== "recent") return;
+
+
const observer = new IntersectionObserver(
+
(entries) => {
+
if (entries[0].isIntersecting && hasNext && !isLoadingNext) {
+
loadNext(20);
+
}
+
},
+
{ threshold: 0.1 }
+
);
+
+
observer.observe(loadMoreRef.current);
+
+
return () => observer.disconnect();
+
}, [hasNext, isLoadingNext, loadNext, activeTab]);
+
+
// Group plays by date
+
const groupedPlays: { date: string; plays: typeof plays }[] = [];
+
let currentDate = "";
+
+
plays.forEach((play) => {
+
if (!play?.playedTime) return;
+
+
const playDate = new Date(play.playedTime).toLocaleDateString("en-US", {
+
weekday: "long",
+
day: "numeric",
+
month: "long",
+
year: "numeric",
+
});
+
+
if (playDate !== currentDate) {
+
currentDate = playDate;
+
groupedPlays.push({ date: playDate, plays: [play] });
+
} else {
+
groupedPlays[groupedPlays.length - 1].plays.push(play);
+
}
+
});
+
+
return (
+
<div className="min-h-screen bg-zinc-950 text-zinc-300 font-mono">
+
<div className="max-w-4xl mx-auto px-6 py-12">
+
<div className="mb-12 flex items-end justify-between border-b border-zinc-800 pb-6">
+
<div>
+
<h1 className="text-xs font-medium uppercase tracking-wider text-zinc-500">Listening History</h1>
+
<p className="text-xs text-zinc-600 mt-1">fm.teal.alpha.feed.play</p>
+
</div>
+
+
<div className="flex gap-4 text-xs">
+
<button
+
onClick={() => setActiveTab("recent")}
+
className={`px-2 py-1 transition-colors ${
+
activeTab === "recent"
+
? "text-zinc-400"
+
: "text-zinc-500 hover:text-zinc-300"
+
}`}
+
>
+
Recent
+
</button>
+
<button
+
onClick={() => setActiveTab("tracks")}
+
className={`px-2 py-1 transition-colors ${
+
activeTab === "tracks"
+
? "text-zinc-400"
+
: "text-zinc-500 hover:text-zinc-300"
+
}`}
+
>
+
Top Tracks
+
</button>
+
<button
+
onClick={() => setActiveTab("albums")}
+
className={`px-2 py-1 transition-colors ${
+
activeTab === "albums"
+
? "text-zinc-400"
+
: "text-zinc-500 hover:text-zinc-300"
+
}`}
+
>
+
Top Albums
+
</button>
+
</div>
+
</div>
+
+
{activeTab === "recent" ? (
+
<>
+
<div className="mb-8">
+
<p className="text-xs text-zinc-500 uppercase tracking-wider">
+
{data?.fmTealAlphaFeedPlays?.totalCount?.toLocaleString()} scrobbles
+
</p>
+
</div>
+
+
<div>
+
{groupedPlays.map((group, groupIndex) => (
+
<div key={groupIndex} className="mb-12">
+
<h2 className="text-xs text-zinc-600 font-medium mb-6 uppercase tracking-wider">
+
{group.date}
+
</h2>
+
<div className="space-y-1">
+
{group.plays.map((play, index) => (
+
<TrackItem key={index} play={play} />
+
))}
+
</div>
+
</div>
+
))}
+
</div>
+
+
{hasNext && (
+
<div ref={loadMoreRef} className="py-12 text-center">
+
{isLoadingNext ? (
+
<p className="text-xs text-zinc-600 uppercase tracking-wider">Loading...</p>
+
) : (
+
<p className="text-xs text-zinc-700 uppercase tracking-wider">·</p>
+
)}
+
</div>
+
)}
+
</>
+
) : activeTab === "tracks" ? (
+
<TopTracks />
+
) : (
+
<TopAlbums />
+
)}
+
</div>
+
</div>
+
);
+
}
+3
src/LoadingFallback.tsx
···
+
export default function LoadingFallback() {
+
return <div className="min-h-screen bg-zinc-950" />;
+
}
+148
src/Profile.tsx
···
+
import { graphql, useLazyLoadQuery, usePaginationFragment } from "react-relay";
+
import { useParams, Link } from "react-router-dom";
+
import { useEffect, useRef } from "react";
+
import type { ProfileQuery as ProfileQueryType } from "./__generated__/ProfileQuery.graphql";
+
import type { Profile_plays$key } from "./__generated__/Profile_plays.graphql";
+
import TrackItem from "./TrackItem";
+
+
export default function Profile() {
+
const { handle } = useParams<{ handle: string }>();
+
+
const queryData = useLazyLoadQuery<ProfileQueryType>(
+
graphql`
+
query ProfileQuery($where: JSON!) {
+
...Profile_plays @arguments(where: $where)
+
}
+
`,
+
{
+
where: { actorHandle: { eq: handle } },
+
}
+
);
+
+
const { data, loadNext, hasNext, isLoadingNext } = usePaginationFragment<
+
ProfileQueryType,
+
Profile_plays$key
+
>(
+
graphql`
+
fragment Profile_plays on Query
+
@refetchable(queryName: "ProfilePaginationQuery")
+
@argumentDefinitions(
+
cursor: { type: "String" }
+
count: { type: "Int", defaultValue: 20 }
+
where: { type: "JSON!" }
+
) {
+
fmTealAlphaFeedPlays(
+
first: $count
+
after: $cursor
+
sortBy: [{ field: "playedTime", direction: desc }]
+
where: $where
+
)
+
@connection(
+
key: "Profile_fmTealAlphaFeedPlays"
+
filters: ["where", "sortBy"]
+
) {
+
totalCount
+
edges {
+
node {
+
...TrackItem_play
+
actorHandle
+
appBskyActorProfile {
+
displayName
+
description
+
avatar {
+
url(preset: "avatar")
+
}
+
}
+
}
+
}
+
}
+
}
+
`,
+
queryData
+
);
+
+
const loadMoreRef = useRef<HTMLDivElement>(null);
+
+
const plays = data?.fmTealAlphaFeedPlays?.edges?.map((edge) => edge.node).filter((n) => n != null) || [];
+
const profile = plays?.[0]?.appBskyActorProfile;
+
+
useEffect(() => {
+
window.scrollTo(0, 0);
+
}, [handle]);
+
+
useEffect(() => {
+
if (!loadMoreRef.current || !hasNext) return;
+
+
const observer = new IntersectionObserver(
+
(entries) => {
+
if (entries[0].isIntersecting && hasNext && !isLoadingNext) {
+
loadNext(20);
+
}
+
},
+
{ threshold: 0.1 }
+
);
+
+
observer.observe(loadMoreRef.current);
+
+
return () => observer.disconnect();
+
}, [hasNext, isLoadingNext, loadNext]);
+
+
return (
+
<div className="min-h-screen bg-zinc-950 text-zinc-300 font-mono">
+
<div className="max-w-4xl mx-auto px-6 py-12">
+
<Link
+
to="/"
+
className="px-2 py-1 text-xs text-zinc-500 hover:text-zinc-300 transition-colors inline-block mb-8"
+
>
+
← Back
+
</Link>
+
+
<div className="mb-12 flex items-start gap-6 border-b border-zinc-800 pb-6">
+
{profile?.avatar?.url && (
+
<img
+
src={profile.avatar.url}
+
alt={profile.displayName ?? handle ?? "User"}
+
className="w-16 h-16 flex-shrink-0 object-cover"
+
/>
+
)}
+
<div className="flex-1">
+
<h1 className="text-lg font-medium mb-1 text-zinc-100">
+
{profile?.displayName ?? handle}
+
</h1>
+
<p className="text-xs text-zinc-500 mb-2">@{handle}</p>
+
{profile?.description && (
+
<p className="text-xs text-zinc-400">{profile.description}</p>
+
)}
+
</div>
+
</div>
+
+
<div className="mb-8">
+
<h2 className="text-sm font-medium uppercase tracking-wider text-zinc-400 mb-2">Recent Tracks</h2>
+
<p className="text-xs text-zinc-500 uppercase tracking-wider">
+
{(data?.fmTealAlphaFeedPlays?.totalCount ?? 0).toLocaleString()} scrobbles
+
</p>
+
</div>
+
+
<div className="space-y-1">
+
{plays && plays.length > 0 ? (
+
plays.map((play, index) => <TrackItem key={index} play={play} />)
+
) : (
+
<p className="text-zinc-600 text-center py-8 text-xs uppercase tracking-wider">
+
No tracks found for this user
+
</p>
+
)}
+
</div>
+
+
{hasNext && (
+
<div ref={loadMoreRef} className="py-12 text-center">
+
{isLoadingNext ? (
+
<p className="text-xs text-zinc-600 uppercase tracking-wider">Loading...</p>
+
) : (
+
<p className="text-xs text-zinc-700 uppercase tracking-wider">·</p>
+
)}
+
</div>
+
)}
+
</div>
+
</div>
+
);
+
}
+65
src/TopAlbums.tsx
···
+
import { graphql, useLazyLoadQuery } from "react-relay";
+
import type { TopAlbumsQuery } from "./__generated__/TopAlbumsQuery.graphql";
+
import AlbumItem from "./AlbumItem";
+
+
export default function TopAlbums() {
+
const data = useLazyLoadQuery<TopAlbumsQuery>(
+
graphql`
+
query TopAlbumsQuery {
+
fmTealAlphaFeedPlaysAggregated(
+
groupBy: ["releaseMbId", "releaseName", "artists"]
+
orderBy: { count: desc }
+
limit: 100
+
) {
+
releaseMbId
+
releaseName
+
artists
+
count
+
}
+
}
+
`,
+
{}
+
);
+
+
const albums = [...(data.fmTealAlphaFeedPlaysAggregated || [])];
+
+
// Deduplicate by release name, keeping the one with highest count
+
// Prefer entries with artist data
+
const seenNames = new Set<string>();
+
const dedupedAlbums = albums
+
.sort((a, b) => {
+
// First sort by count (already sorted from query)
+
if (b.count !== a.count) return b.count - a.count;
+
// Then prefer entries with artists data
+
if (a.artists && !b.artists) return -1;
+
if (!a.artists && b.artists) return 1;
+
return 0;
+
})
+
.filter((album) => {
+
const name = album.releaseName || "Unknown Album";
+
if (seenNames.has(name)) {
+
return false;
+
}
+
seenNames.add(name);
+
return true;
+
})
+
.slice(0, 50);
+
+
const maxCount = dedupedAlbums.length > 0 ? dedupedAlbums[0].count : 0;
+
+
return (
+
<div className="space-y-1">
+
{dedupedAlbums.map((album, index) => (
+
<AlbumItem
+
key={album.releaseMbId || index}
+
releaseName={album.releaseName || "Unknown Album"}
+
releaseMbId={album.releaseMbId}
+
artists={album.artists}
+
count={album.count}
+
rank={index + 1}
+
maxCount={maxCount}
+
/>
+
))}
+
</div>
+
);
+
}
+91
src/TopTrackItem.tsx
···
+
import { useAlbumArt } from "./useAlbumArt";
+
+
interface Artist {
+
artistName: string;
+
}
+
+
interface TopTrackItemProps {
+
trackName: string;
+
releaseMbId: string | null | undefined;
+
artists: string | null | undefined;
+
count: number;
+
rank: number;
+
maxCount: number;
+
}
+
+
export default function TopTrackItem({
+
trackName,
+
releaseMbId,
+
artists,
+
count,
+
rank,
+
maxCount,
+
}: TopTrackItemProps) {
+
const { albumArtUrl, isLoading } = useAlbumArt(releaseMbId);
+
const barWidth = maxCount > 0 ? (count / maxCount) * 100 : 0;
+
+
// Parse artists JSON
+
let artistNames = "Unknown Artist";
+
if (artists) {
+
try {
+
const parsed = typeof artists === 'string' ? JSON.parse(artists) : artists;
+
if (Array.isArray(parsed)) {
+
artistNames = parsed.map((a: Artist) => a.artistName).join(", ");
+
} else if (typeof parsed === 'string') {
+
artistNames = parsed;
+
}
+
} catch {
+
artistNames = String(artists);
+
}
+
}
+
+
return (
+
<div className="group py-3 px-4 hover:bg-zinc-900/50 transition-colors relative overflow-hidden">
+
<div
+
className="absolute inset-y-0 left-0 bg-violet-500/10 transition-all"
+
style={{ width: `${barWidth}%` }}
+
/>
+
<div className="flex items-center gap-4 relative">
+
<div className="text-xs text-zinc-600 w-8 text-right flex-shrink-0 font-medium">
+
{rank}
+
</div>
+
+
<div className="flex-shrink-0">
+
{isLoading ? (
+
<div className="w-10 h-10 bg-zinc-800 animate-pulse" />
+
) : albumArtUrl ? (
+
<img
+
src={albumArtUrl}
+
alt={`${trackName} album art`}
+
className="w-10 h-10 object-cover"
+
loading="lazy"
+
/>
+
) : (
+
<div className="w-10 h-10 bg-zinc-800 flex items-center justify-center">
+
<svg
+
className="w-5 h-5 text-zinc-600"
+
fill="currentColor"
+
viewBox="0 0 20 20"
+
>
+
<path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" />
+
</svg>
+
</div>
+
)}
+
</div>
+
+
<div className="flex-1 min-w-0">
+
<h3 className="text-sm font-medium text-zinc-100 truncate">
+
{trackName}
+
</h3>
+
<p className="text-xs text-zinc-500 truncate">{artistNames}</p>
+
</div>
+
+
<div className="text-right flex-shrink-0">
+
<p className="text-xs text-zinc-400 font-medium">
+
{count.toLocaleString()}
+
</p>
+
</div>
+
</div>
+
</div>
+
);
+
}
+42
src/TopTracks.tsx
···
+
import { graphql, useLazyLoadQuery } from "react-relay";
+
import type { TopTracksQuery } from "./__generated__/TopTracksQuery.graphql";
+
import TopTrackItem from "./TopTrackItem";
+
+
export default function TopTracks() {
+
const data = useLazyLoadQuery<TopTracksQuery>(
+
graphql`
+
query TopTracksQuery {
+
fmTealAlphaFeedPlaysAggregated(
+
groupBy: ["trackName", "releaseMbId", "artists"]
+
orderBy: { count: desc }
+
limit: 50
+
) {
+
trackName
+
releaseMbId
+
artists
+
count
+
}
+
}
+
`,
+
{}
+
);
+
+
const tracks = data.fmTealAlphaFeedPlaysAggregated || [];
+
const maxCount = tracks.length > 0 ? tracks[0].count : 0;
+
+
return (
+
<div className="space-y-1">
+
{tracks.map((track, index) => (
+
<TopTrackItem
+
key={`${track.trackName}-${index}`}
+
trackName={track.trackName || "Unknown Track"}
+
releaseMbId={track.releaseMbId}
+
artists={track.artists || "Unknown Artist"}
+
count={track.count}
+
rank={index + 1}
+
maxCount={maxCount}
+
/>
+
))}
+
</div>
+
);
+
}
+88
src/TrackItem.tsx
···
+
import { graphql, useFragment } from "react-relay";
+
import type { TrackItem_play$key } from "./__generated__/TrackItem_play.graphql";
+
import { useAlbumArt } from "./useAlbumArt";
+
+
interface TrackItemProps {
+
play: TrackItem_play$key;
+
}
+
+
export default function TrackItem({ play }: TrackItemProps) {
+
const data = useFragment(
+
graphql`
+
fragment TrackItem_play on FmTealAlphaFeedPlay {
+
trackName
+
playedTime
+
artists
+
releaseName
+
releaseMbId
+
actorHandle
+
appBskyActorProfile {
+
displayName
+
}
+
}
+
`,
+
play
+
);
+
+
const { albumArtUrl, isLoading } = useAlbumArt(data.releaseMbId);
+
+
return (
+
<div className="group py-3 px-4 hover:bg-zinc-900/50 transition-colors">
+
<div className="flex items-center gap-4">
+
<div className="flex-shrink-0">
+
{isLoading ? (
+
<div className="w-10 h-10 bg-zinc-800 animate-pulse" />
+
) : albumArtUrl ? (
+
<img
+
src={albumArtUrl}
+
alt={`${data.trackName} album art`}
+
className="w-10 h-10 object-cover"
+
loading="lazy"
+
/>
+
) : (
+
<div className="w-10 h-10 bg-zinc-800 flex items-center justify-center">
+
<svg className="w-5 h-5 text-zinc-600" fill="currentColor" viewBox="0 0 20 20">
+
<path d="M18 3a1 1 0 00-1.196-.98l-10 2A1 1 0 006 5v9.114A4.369 4.369 0 005 14c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V7.82l8-1.6v5.894A4.37 4.37 0 0015 12c-1.657 0-3 .895-3 2s1.343 2 3 2 3-.895 3-2V3z" />
+
</svg>
+
</div>
+
)}
+
</div>
+
+
<div className="flex-1 min-w-0 grid grid-cols-2 gap-4">
+
<div className="min-w-0">
+
<h3 className="text-sm font-medium text-zinc-100 truncate">
+
{data.trackName}
+
</h3>
+
<p className="text-xs text-zinc-500 truncate">
+
{Array.isArray(data.artists)
+
? data.artists.map((a) => a.artistName).join(", ")
+
: data.artists}
+
</p>
+
</div>
+
+
<div className="text-right min-w-0">
+
<p className="text-xs text-zinc-400 truncate">
+
{data.releaseName}
+
</p>
+
<div className="flex items-center justify-end gap-2 mt-0.5 min-w-0 overflow-hidden">
+
{data.playedTime && (
+
<p className="text-xs text-zinc-600 flex-shrink-0">
+
{new Date(data.playedTime).toLocaleTimeString("en-US", {
+
hour: "numeric",
+
minute: "2-digit",
+
})}
+
</p>
+
)}
+
<a
+
href={`/profile/${data.actorHandle}`}
+
className="text-xs text-violet-500 hover:text-violet-400 transition-colors truncate block max-w-[120px]"
+
>
+
@{data.actorHandle}
+
</a>
+
</div>
+
</div>
+
</div>
+
</div>
+
</div>
+
);
+
}
+258
src/__generated__/AppPaginationQuery.graphql.ts
···
+
/**
+
* @generated SignedSource<<cef2df106afea24fa8527f2def8e9991>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ConcreteRequest } from 'relay-runtime';
+
import { FragmentRefs } from "relay-runtime";
+
export type AppPaginationQuery$variables = {
+
count?: number | null | undefined;
+
cursor?: string | null | undefined;
+
};
+
export type AppPaginationQuery$data = {
+
readonly " $fragmentSpreads": FragmentRefs<"App_plays">;
+
};
+
export type AppPaginationQuery = {
+
response: AppPaginationQuery$data;
+
variables: AppPaginationQuery$variables;
+
};
+
+
const node: ConcreteRequest = (function(){
+
var v0 = [
+
{
+
"defaultValue": 20,
+
"kind": "LocalArgument",
+
"name": "count"
+
},
+
{
+
"defaultValue": null,
+
"kind": "LocalArgument",
+
"name": "cursor"
+
}
+
],
+
v1 = [
+
{
+
"kind": "Variable",
+
"name": "after",
+
"variableName": "cursor"
+
},
+
{
+
"kind": "Variable",
+
"name": "first",
+
"variableName": "count"
+
},
+
{
+
"kind": "Literal",
+
"name": "sortBy",
+
"value": [
+
{
+
"direction": "desc",
+
"field": "playedTime"
+
}
+
]
+
}
+
];
+
return {
+
"fragment": {
+
"argumentDefinitions": (v0/*: any*/),
+
"kind": "Fragment",
+
"metadata": null,
+
"name": "AppPaginationQuery",
+
"selections": [
+
{
+
"args": [
+
{
+
"kind": "Variable",
+
"name": "count",
+
"variableName": "count"
+
},
+
{
+
"kind": "Variable",
+
"name": "cursor",
+
"variableName": "cursor"
+
}
+
],
+
"kind": "FragmentSpread",
+
"name": "App_plays"
+
}
+
],
+
"type": "Query",
+
"abstractKey": null
+
},
+
"kind": "Request",
+
"operation": {
+
"argumentDefinitions": (v0/*: any*/),
+
"kind": "Operation",
+
"name": "AppPaginationQuery",
+
"selections": [
+
{
+
"alias": null,
+
"args": (v1/*: any*/),
+
"concreteType": "FmTealAlphaFeedPlayConnection",
+
"kind": "LinkedField",
+
"name": "fmTealAlphaFeedPlays",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "totalCount",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlayEdge",
+
"kind": "LinkedField",
+
"name": "edges",
+
"plural": true,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlay",
+
"kind": "LinkedField",
+
"name": "node",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "playedTime",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "trackName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "artists",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseMbId",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "actorHandle",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "AppBskyActorProfile",
+
"kind": "LinkedField",
+
"name": "appBskyActorProfile",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "displayName",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "__typename",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "cursor",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "PageInfo",
+
"kind": "LinkedField",
+
"name": "pageInfo",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "endCursor",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "hasNextPage",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": (v1/*: any*/),
+
"filters": [
+
"sortBy"
+
],
+
"handle": "connection",
+
"key": "App_fmTealAlphaFeedPlays",
+
"kind": "LinkedHandle",
+
"name": "fmTealAlphaFeedPlays"
+
}
+
]
+
},
+
"params": {
+
"cacheID": "e115a73de49cf6f84a35f172a7910c5c",
+
"id": null,
+
"metadata": {},
+
"name": "AppPaginationQuery",
+
"operationKind": "query",
+
"text": "query AppPaginationQuery(\n $count: Int = 20\n $cursor: String\n) {\n ...App_plays_1G22uz\n}\n\nfragment App_plays_1G22uz on Query {\n fmTealAlphaFeedPlays(first: $count, after: $cursor, sortBy: [{field: \"playedTime\", direction: desc}]) {\n totalCount\n edges {\n node {\n playedTime\n ...TrackItem_play\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TrackItem_play on FmTealAlphaFeedPlay {\n trackName\n playedTime\n artists\n releaseName\n releaseMbId\n actorHandle\n appBskyActorProfile {\n displayName\n }\n}\n"
+
}
+
};
+
})();
+
+
(node as any).hash = "0e4acf96fedae07af90ce6e9e3bf18d6";
+
+
export default node;
+227
src/__generated__/AppQuery.graphql.ts
···
+
/**
+
* @generated SignedSource<<541e6114682aef7988bd233592085337>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ConcreteRequest } from 'relay-runtime';
+
import { FragmentRefs } from "relay-runtime";
+
export type AppQuery$variables = Record<PropertyKey, never>;
+
export type AppQuery$data = {
+
readonly " $fragmentSpreads": FragmentRefs<"App_plays">;
+
};
+
export type AppQuery = {
+
response: AppQuery$data;
+
variables: AppQuery$variables;
+
};
+
+
const node: ConcreteRequest = (function(){
+
var v0 = [
+
{
+
"kind": "Literal",
+
"name": "first",
+
"value": 20
+
},
+
{
+
"kind": "Literal",
+
"name": "sortBy",
+
"value": [
+
{
+
"direction": "desc",
+
"field": "playedTime"
+
}
+
]
+
}
+
];
+
return {
+
"fragment": {
+
"argumentDefinitions": [],
+
"kind": "Fragment",
+
"metadata": null,
+
"name": "AppQuery",
+
"selections": [
+
{
+
"args": null,
+
"kind": "FragmentSpread",
+
"name": "App_plays"
+
}
+
],
+
"type": "Query",
+
"abstractKey": null
+
},
+
"kind": "Request",
+
"operation": {
+
"argumentDefinitions": [],
+
"kind": "Operation",
+
"name": "AppQuery",
+
"selections": [
+
{
+
"alias": null,
+
"args": (v0/*: any*/),
+
"concreteType": "FmTealAlphaFeedPlayConnection",
+
"kind": "LinkedField",
+
"name": "fmTealAlphaFeedPlays",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "totalCount",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlayEdge",
+
"kind": "LinkedField",
+
"name": "edges",
+
"plural": true,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlay",
+
"kind": "LinkedField",
+
"name": "node",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "playedTime",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "trackName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "artists",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseMbId",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "actorHandle",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "AppBskyActorProfile",
+
"kind": "LinkedField",
+
"name": "appBskyActorProfile",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "displayName",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "__typename",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "cursor",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "PageInfo",
+
"kind": "LinkedField",
+
"name": "pageInfo",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "endCursor",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "hasNextPage",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": "fmTealAlphaFeedPlays(first:20,sortBy:[{\"direction\":\"desc\",\"field\":\"playedTime\"}])"
+
},
+
{
+
"alias": null,
+
"args": (v0/*: any*/),
+
"filters": [
+
"sortBy"
+
],
+
"handle": "connection",
+
"key": "App_fmTealAlphaFeedPlays",
+
"kind": "LinkedHandle",
+
"name": "fmTealAlphaFeedPlays"
+
}
+
]
+
},
+
"params": {
+
"cacheID": "1cacfb0aa5545cf84688b8396079b855",
+
"id": null,
+
"metadata": {},
+
"name": "AppQuery",
+
"operationKind": "query",
+
"text": "query AppQuery {\n ...App_plays\n}\n\nfragment App_plays on Query {\n fmTealAlphaFeedPlays(first: 20, sortBy: [{field: \"playedTime\", direction: desc}]) {\n totalCount\n edges {\n node {\n playedTime\n ...TrackItem_play\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TrackItem_play on FmTealAlphaFeedPlay {\n trackName\n playedTime\n artists\n releaseName\n releaseMbId\n actorHandle\n appBskyActorProfile {\n displayName\n }\n}\n"
+
}
+
};
+
})();
+
+
(node as any).hash = "4b1837f6cd874e31461fbead77c1b012";
+
+
export default node;
+184
src/__generated__/App_plays.graphql.ts
···
+
/**
+
* @generated SignedSource<<a3ae5f31f618986fb12e6c57458c9853>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ReaderFragment } from 'relay-runtime';
+
import { FragmentRefs } from "relay-runtime";
+
export type App_plays$data = {
+
readonly fmTealAlphaFeedPlays: {
+
readonly edges: ReadonlyArray<{
+
readonly node: {
+
readonly playedTime: string | null | undefined;
+
readonly " $fragmentSpreads": FragmentRefs<"TrackItem_play">;
+
};
+
}>;
+
readonly totalCount: number;
+
};
+
readonly " $fragmentType": "App_plays";
+
};
+
export type App_plays$key = {
+
readonly " $data"?: App_plays$data;
+
readonly " $fragmentSpreads": FragmentRefs<"App_plays">;
+
};
+
+
import AppPaginationQuery_graphql from './AppPaginationQuery.graphql';
+
+
const node: ReaderFragment = (function(){
+
var v0 = [
+
"fmTealAlphaFeedPlays"
+
];
+
return {
+
"argumentDefinitions": [
+
{
+
"defaultValue": 20,
+
"kind": "LocalArgument",
+
"name": "count"
+
},
+
{
+
"defaultValue": null,
+
"kind": "LocalArgument",
+
"name": "cursor"
+
}
+
],
+
"kind": "Fragment",
+
"metadata": {
+
"connection": [
+
{
+
"count": "count",
+
"cursor": "cursor",
+
"direction": "forward",
+
"path": (v0/*: any*/)
+
}
+
],
+
"refetch": {
+
"connection": {
+
"forward": {
+
"count": "count",
+
"cursor": "cursor"
+
},
+
"backward": null,
+
"path": (v0/*: any*/)
+
},
+
"fragmentPathInResult": [],
+
"operation": AppPaginationQuery_graphql
+
}
+
},
+
"name": "App_plays",
+
"selections": [
+
{
+
"alias": "fmTealAlphaFeedPlays",
+
"args": [
+
{
+
"kind": "Literal",
+
"name": "sortBy",
+
"value": [
+
{
+
"direction": "desc",
+
"field": "playedTime"
+
}
+
]
+
}
+
],
+
"concreteType": "FmTealAlphaFeedPlayConnection",
+
"kind": "LinkedField",
+
"name": "__App_fmTealAlphaFeedPlays_connection",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "totalCount",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlayEdge",
+
"kind": "LinkedField",
+
"name": "edges",
+
"plural": true,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlay",
+
"kind": "LinkedField",
+
"name": "node",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "playedTime",
+
"storageKey": null
+
},
+
{
+
"args": null,
+
"kind": "FragmentSpread",
+
"name": "TrackItem_play"
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "__typename",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "cursor",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "PageInfo",
+
"kind": "LinkedField",
+
"name": "pageInfo",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "endCursor",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "hasNextPage",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": "__App_fmTealAlphaFeedPlays_connection(sortBy:[{\"direction\":\"desc\",\"field\":\"playedTime\"}])"
+
}
+
],
+
"type": "Query",
+
"abstractKey": null
+
};
+
})();
+
+
(node as any).hash = "0e4acf96fedae07af90ce6e9e3bf18d6";
+
+
export default node;
+303
src/__generated__/ProfilePaginationQuery.graphql.ts
···
+
/**
+
* @generated SignedSource<<97625934b32c4079cc58877234aeac04>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ConcreteRequest } from 'relay-runtime';
+
import { FragmentRefs } from "relay-runtime";
+
export type ProfilePaginationQuery$variables = {
+
count?: number | null | undefined;
+
cursor?: string | null | undefined;
+
where: any;
+
};
+
export type ProfilePaginationQuery$data = {
+
readonly " $fragmentSpreads": FragmentRefs<"Profile_plays">;
+
};
+
export type ProfilePaginationQuery = {
+
response: ProfilePaginationQuery$data;
+
variables: ProfilePaginationQuery$variables;
+
};
+
+
const node: ConcreteRequest = (function(){
+
var v0 = [
+
{
+
"defaultValue": 20,
+
"kind": "LocalArgument",
+
"name": "count"
+
},
+
{
+
"defaultValue": null,
+
"kind": "LocalArgument",
+
"name": "cursor"
+
},
+
{
+
"defaultValue": null,
+
"kind": "LocalArgument",
+
"name": "where"
+
}
+
],
+
v1 = {
+
"kind": "Variable",
+
"name": "where",
+
"variableName": "where"
+
},
+
v2 = [
+
{
+
"kind": "Variable",
+
"name": "after",
+
"variableName": "cursor"
+
},
+
{
+
"kind": "Variable",
+
"name": "first",
+
"variableName": "count"
+
},
+
{
+
"kind": "Literal",
+
"name": "sortBy",
+
"value": [
+
{
+
"direction": "desc",
+
"field": "playedTime"
+
}
+
]
+
},
+
(v1/*: any*/)
+
];
+
return {
+
"fragment": {
+
"argumentDefinitions": (v0/*: any*/),
+
"kind": "Fragment",
+
"metadata": null,
+
"name": "ProfilePaginationQuery",
+
"selections": [
+
{
+
"args": [
+
{
+
"kind": "Variable",
+
"name": "count",
+
"variableName": "count"
+
},
+
{
+
"kind": "Variable",
+
"name": "cursor",
+
"variableName": "cursor"
+
},
+
(v1/*: any*/)
+
],
+
"kind": "FragmentSpread",
+
"name": "Profile_plays"
+
}
+
],
+
"type": "Query",
+
"abstractKey": null
+
},
+
"kind": "Request",
+
"operation": {
+
"argumentDefinitions": (v0/*: any*/),
+
"kind": "Operation",
+
"name": "ProfilePaginationQuery",
+
"selections": [
+
{
+
"alias": null,
+
"args": (v2/*: any*/),
+
"concreteType": "FmTealAlphaFeedPlayConnection",
+
"kind": "LinkedField",
+
"name": "fmTealAlphaFeedPlays",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "totalCount",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlayEdge",
+
"kind": "LinkedField",
+
"name": "edges",
+
"plural": true,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlay",
+
"kind": "LinkedField",
+
"name": "node",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "trackName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "playedTime",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "artists",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseMbId",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "actorHandle",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "AppBskyActorProfile",
+
"kind": "LinkedField",
+
"name": "appBskyActorProfile",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "displayName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "description",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "Blob",
+
"kind": "LinkedField",
+
"name": "avatar",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": [
+
{
+
"kind": "Literal",
+
"name": "preset",
+
"value": "avatar"
+
}
+
],
+
"kind": "ScalarField",
+
"name": "url",
+
"storageKey": "url(preset:\"avatar\")"
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "__typename",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "cursor",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "PageInfo",
+
"kind": "LinkedField",
+
"name": "pageInfo",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "endCursor",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "hasNextPage",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": (v2/*: any*/),
+
"filters": [
+
"where",
+
"sortBy"
+
],
+
"handle": "connection",
+
"key": "Profile_fmTealAlphaFeedPlays",
+
"kind": "LinkedHandle",
+
"name": "fmTealAlphaFeedPlays"
+
}
+
]
+
},
+
"params": {
+
"cacheID": "08e603fb4052c3556739bda428413453",
+
"id": null,
+
"metadata": {},
+
"name": "ProfilePaginationQuery",
+
"operationKind": "query",
+
"text": "query ProfilePaginationQuery(\n $count: Int = 20\n $cursor: String\n $where: JSON!\n) {\n ...Profile_plays_mjR8k\n}\n\nfragment Profile_plays_mjR8k on Query {\n fmTealAlphaFeedPlays(first: $count, after: $cursor, sortBy: [{field: \"playedTime\", direction: desc}], where: $where) {\n totalCount\n edges {\n node {\n ...TrackItem_play\n actorHandle\n appBskyActorProfile {\n displayName\n description\n avatar {\n url(preset: \"avatar\")\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TrackItem_play on FmTealAlphaFeedPlay {\n trackName\n playedTime\n artists\n releaseName\n releaseMbId\n actorHandle\n appBskyActorProfile {\n displayName\n }\n}\n"
+
}
+
};
+
})();
+
+
(node as any).hash = "474168bb0d13417c1b7067c09a82f7a2";
+
+
export default node;
+276
src/__generated__/ProfileQuery.graphql.ts
···
+
/**
+
* @generated SignedSource<<c47dafb8d21963c2a9dcbcd54d7bd8d8>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ConcreteRequest } from 'relay-runtime';
+
import { FragmentRefs } from "relay-runtime";
+
export type ProfileQuery$variables = {
+
where: any;
+
};
+
export type ProfileQuery$data = {
+
readonly " $fragmentSpreads": FragmentRefs<"Profile_plays">;
+
};
+
export type ProfileQuery = {
+
response: ProfileQuery$data;
+
variables: ProfileQuery$variables;
+
};
+
+
const node: ConcreteRequest = (function(){
+
var v0 = [
+
{
+
"defaultValue": null,
+
"kind": "LocalArgument",
+
"name": "where"
+
}
+
],
+
v1 = {
+
"kind": "Variable",
+
"name": "where",
+
"variableName": "where"
+
},
+
v2 = [
+
{
+
"kind": "Literal",
+
"name": "first",
+
"value": 20
+
},
+
{
+
"kind": "Literal",
+
"name": "sortBy",
+
"value": [
+
{
+
"direction": "desc",
+
"field": "playedTime"
+
}
+
]
+
},
+
(v1/*: any*/)
+
];
+
return {
+
"fragment": {
+
"argumentDefinitions": (v0/*: any*/),
+
"kind": "Fragment",
+
"metadata": null,
+
"name": "ProfileQuery",
+
"selections": [
+
{
+
"args": [
+
(v1/*: any*/)
+
],
+
"kind": "FragmentSpread",
+
"name": "Profile_plays"
+
}
+
],
+
"type": "Query",
+
"abstractKey": null
+
},
+
"kind": "Request",
+
"operation": {
+
"argumentDefinitions": (v0/*: any*/),
+
"kind": "Operation",
+
"name": "ProfileQuery",
+
"selections": [
+
{
+
"alias": null,
+
"args": (v2/*: any*/),
+
"concreteType": "FmTealAlphaFeedPlayConnection",
+
"kind": "LinkedField",
+
"name": "fmTealAlphaFeedPlays",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "totalCount",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlayEdge",
+
"kind": "LinkedField",
+
"name": "edges",
+
"plural": true,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlay",
+
"kind": "LinkedField",
+
"name": "node",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "trackName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "playedTime",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "artists",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseMbId",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "actorHandle",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "AppBskyActorProfile",
+
"kind": "LinkedField",
+
"name": "appBskyActorProfile",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "displayName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "description",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "Blob",
+
"kind": "LinkedField",
+
"name": "avatar",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": [
+
{
+
"kind": "Literal",
+
"name": "preset",
+
"value": "avatar"
+
}
+
],
+
"kind": "ScalarField",
+
"name": "url",
+
"storageKey": "url(preset:\"avatar\")"
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "__typename",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "cursor",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "PageInfo",
+
"kind": "LinkedField",
+
"name": "pageInfo",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "endCursor",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "hasNextPage",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": (v2/*: any*/),
+
"filters": [
+
"where",
+
"sortBy"
+
],
+
"handle": "connection",
+
"key": "Profile_fmTealAlphaFeedPlays",
+
"kind": "LinkedHandle",
+
"name": "fmTealAlphaFeedPlays"
+
}
+
]
+
},
+
"params": {
+
"cacheID": "3137e7b6ec5148299e7a7c7f4edf07b2",
+
"id": null,
+
"metadata": {},
+
"name": "ProfileQuery",
+
"operationKind": "query",
+
"text": "query ProfileQuery(\n $where: JSON!\n) {\n ...Profile_plays_3FC4Qo\n}\n\nfragment Profile_plays_3FC4Qo on Query {\n fmTealAlphaFeedPlays(first: 20, sortBy: [{field: \"playedTime\", direction: desc}], where: $where) {\n totalCount\n edges {\n node {\n ...TrackItem_play\n actorHandle\n appBskyActorProfile {\n displayName\n description\n avatar {\n url(preset: \"avatar\")\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment TrackItem_play on FmTealAlphaFeedPlay {\n trackName\n playedTime\n artists\n releaseName\n releaseMbId\n actorHandle\n appBskyActorProfile {\n displayName\n }\n}\n"
+
}
+
};
+
})();
+
+
(node as any).hash = "267039e382b3b95a739ff3cdced3211e";
+
+
export default node;
+250
src/__generated__/Profile_plays.graphql.ts
···
+
/**
+
* @generated SignedSource<<bd63c9e74b05810076b60ad0e51cb230>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ReaderFragment } from 'relay-runtime';
+
import { FragmentRefs } from "relay-runtime";
+
export type Profile_plays$data = {
+
readonly fmTealAlphaFeedPlays: {
+
readonly edges: ReadonlyArray<{
+
readonly node: {
+
readonly actorHandle: string | null | undefined;
+
readonly appBskyActorProfile: {
+
readonly avatar: {
+
readonly url: string;
+
} | null | undefined;
+
readonly description: string | null | undefined;
+
readonly displayName: string | null | undefined;
+
} | null | undefined;
+
readonly " $fragmentSpreads": FragmentRefs<"TrackItem_play">;
+
};
+
}>;
+
readonly totalCount: number;
+
};
+
readonly " $fragmentType": "Profile_plays";
+
};
+
export type Profile_plays$key = {
+
readonly " $data"?: Profile_plays$data;
+
readonly " $fragmentSpreads": FragmentRefs<"Profile_plays">;
+
};
+
+
import ProfilePaginationQuery_graphql from './ProfilePaginationQuery.graphql';
+
+
const node: ReaderFragment = (function(){
+
var v0 = [
+
"fmTealAlphaFeedPlays"
+
];
+
return {
+
"argumentDefinitions": [
+
{
+
"defaultValue": 20,
+
"kind": "LocalArgument",
+
"name": "count"
+
},
+
{
+
"defaultValue": null,
+
"kind": "LocalArgument",
+
"name": "cursor"
+
},
+
{
+
"defaultValue": null,
+
"kind": "LocalArgument",
+
"name": "where"
+
}
+
],
+
"kind": "Fragment",
+
"metadata": {
+
"connection": [
+
{
+
"count": "count",
+
"cursor": "cursor",
+
"direction": "forward",
+
"path": (v0/*: any*/)
+
}
+
],
+
"refetch": {
+
"connection": {
+
"forward": {
+
"count": "count",
+
"cursor": "cursor"
+
},
+
"backward": null,
+
"path": (v0/*: any*/)
+
},
+
"fragmentPathInResult": [],
+
"operation": ProfilePaginationQuery_graphql
+
}
+
},
+
"name": "Profile_plays",
+
"selections": [
+
{
+
"alias": "fmTealAlphaFeedPlays",
+
"args": [
+
{
+
"kind": "Literal",
+
"name": "sortBy",
+
"value": [
+
{
+
"direction": "desc",
+
"field": "playedTime"
+
}
+
]
+
},
+
{
+
"kind": "Variable",
+
"name": "where",
+
"variableName": "where"
+
}
+
],
+
"concreteType": "FmTealAlphaFeedPlayConnection",
+
"kind": "LinkedField",
+
"name": "__Profile_fmTealAlphaFeedPlays_connection",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "totalCount",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlayEdge",
+
"kind": "LinkedField",
+
"name": "edges",
+
"plural": true,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "FmTealAlphaFeedPlay",
+
"kind": "LinkedField",
+
"name": "node",
+
"plural": false,
+
"selections": [
+
{
+
"args": null,
+
"kind": "FragmentSpread",
+
"name": "TrackItem_play"
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "actorHandle",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "AppBskyActorProfile",
+
"kind": "LinkedField",
+
"name": "appBskyActorProfile",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "displayName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "description",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "Blob",
+
"kind": "LinkedField",
+
"name": "avatar",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": [
+
{
+
"kind": "Literal",
+
"name": "preset",
+
"value": "avatar"
+
}
+
],
+
"kind": "ScalarField",
+
"name": "url",
+
"storageKey": "url(preset:\"avatar\")"
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "__typename",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "cursor",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "PageInfo",
+
"kind": "LinkedField",
+
"name": "pageInfo",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "endCursor",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "hasNextPage",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"type": "Query",
+
"abstractKey": null
+
};
+
})();
+
+
(node as any).hash = "474168bb0d13417c1b7067c09a82f7a2";
+
+
export default node;
+120
src/__generated__/TopAlbumsQuery.graphql.ts
···
+
/**
+
* @generated SignedSource<<a492b6190b60e9be64d199702b76977a>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ConcreteRequest } from 'relay-runtime';
+
export type TopAlbumsQuery$variables = Record<PropertyKey, never>;
+
export type TopAlbumsQuery$data = {
+
readonly fmTealAlphaFeedPlaysAggregated: ReadonlyArray<{
+
readonly artists: string | null | undefined;
+
readonly count: number;
+
readonly releaseMbId: string | null | undefined;
+
readonly releaseName: string | null | undefined;
+
}>;
+
};
+
export type TopAlbumsQuery = {
+
response: TopAlbumsQuery$data;
+
variables: TopAlbumsQuery$variables;
+
};
+
+
const node: ConcreteRequest = (function(){
+
var v0 = [
+
{
+
"alias": null,
+
"args": [
+
{
+
"kind": "Literal",
+
"name": "groupBy",
+
"value": [
+
"releaseMbId",
+
"releaseName",
+
"artists"
+
]
+
},
+
{
+
"kind": "Literal",
+
"name": "limit",
+
"value": 100
+
},
+
{
+
"kind": "Literal",
+
"name": "orderBy",
+
"value": {
+
"count": "desc"
+
}
+
}
+
],
+
"concreteType": "FmTealAlphaFeedPlayAggregated",
+
"kind": "LinkedField",
+
"name": "fmTealAlphaFeedPlaysAggregated",
+
"plural": true,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseMbId",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "artists",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "count",
+
"storageKey": null
+
}
+
],
+
"storageKey": "fmTealAlphaFeedPlaysAggregated(groupBy:[\"releaseMbId\",\"releaseName\",\"artists\"],limit:100,orderBy:{\"count\":\"desc\"})"
+
}
+
];
+
return {
+
"fragment": {
+
"argumentDefinitions": [],
+
"kind": "Fragment",
+
"metadata": null,
+
"name": "TopAlbumsQuery",
+
"selections": (v0/*: any*/),
+
"type": "Query",
+
"abstractKey": null
+
},
+
"kind": "Request",
+
"operation": {
+
"argumentDefinitions": [],
+
"kind": "Operation",
+
"name": "TopAlbumsQuery",
+
"selections": (v0/*: any*/)
+
},
+
"params": {
+
"cacheID": "65b42ff33b8a5de6eb4785e764ae70fc",
+
"id": null,
+
"metadata": {},
+
"name": "TopAlbumsQuery",
+
"operationKind": "query",
+
"text": "query TopAlbumsQuery {\n fmTealAlphaFeedPlaysAggregated(groupBy: [\"releaseMbId\", \"releaseName\", \"artists\"], orderBy: {count: desc}, limit: 100) {\n releaseMbId\n releaseName\n artists\n count\n }\n}\n"
+
}
+
};
+
})();
+
+
(node as any).hash = "b5748a3a4af3140d3cff228e7462f73d";
+
+
export default node;
+120
src/__generated__/TopTracksQuery.graphql.ts
···
+
/**
+
* @generated SignedSource<<ed55197dadbf24b9cf975295d63a2436>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ConcreteRequest } from 'relay-runtime';
+
export type TopTracksQuery$variables = Record<PropertyKey, never>;
+
export type TopTracksQuery$data = {
+
readonly fmTealAlphaFeedPlaysAggregated: ReadonlyArray<{
+
readonly artists: string | null | undefined;
+
readonly count: number;
+
readonly releaseMbId: string | null | undefined;
+
readonly trackName: string | null | undefined;
+
}>;
+
};
+
export type TopTracksQuery = {
+
response: TopTracksQuery$data;
+
variables: TopTracksQuery$variables;
+
};
+
+
const node: ConcreteRequest = (function(){
+
var v0 = [
+
{
+
"alias": null,
+
"args": [
+
{
+
"kind": "Literal",
+
"name": "groupBy",
+
"value": [
+
"trackName",
+
"releaseMbId",
+
"artists"
+
]
+
},
+
{
+
"kind": "Literal",
+
"name": "limit",
+
"value": 50
+
},
+
{
+
"kind": "Literal",
+
"name": "orderBy",
+
"value": {
+
"count": "desc"
+
}
+
}
+
],
+
"concreteType": "FmTealAlphaFeedPlayAggregated",
+
"kind": "LinkedField",
+
"name": "fmTealAlphaFeedPlaysAggregated",
+
"plural": true,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "trackName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseMbId",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "artists",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "count",
+
"storageKey": null
+
}
+
],
+
"storageKey": "fmTealAlphaFeedPlaysAggregated(groupBy:[\"trackName\",\"releaseMbId\",\"artists\"],limit:50,orderBy:{\"count\":\"desc\"})"
+
}
+
];
+
return {
+
"fragment": {
+
"argumentDefinitions": [],
+
"kind": "Fragment",
+
"metadata": null,
+
"name": "TopTracksQuery",
+
"selections": (v0/*: any*/),
+
"type": "Query",
+
"abstractKey": null
+
},
+
"kind": "Request",
+
"operation": {
+
"argumentDefinitions": [],
+
"kind": "Operation",
+
"name": "TopTracksQuery",
+
"selections": (v0/*: any*/)
+
},
+
"params": {
+
"cacheID": "61e9f7886dfe9eaeb599b939f2d636e5",
+
"id": null,
+
"metadata": {},
+
"name": "TopTracksQuery",
+
"operationKind": "query",
+
"text": "query TopTracksQuery {\n fmTealAlphaFeedPlaysAggregated(groupBy: [\"trackName\", \"releaseMbId\", \"artists\"], orderBy: {count: desc}, limit: 50) {\n trackName\n releaseMbId\n artists\n count\n }\n}\n"
+
}
+
};
+
})();
+
+
(node as any).hash = "536f8ddb64daa09017abff121d7ea8ce";
+
+
export default node;
+103
src/__generated__/TrackItem_play.graphql.ts
···
+
/**
+
* @generated SignedSource<<1403d6e3403844ad955c2fe48c8a66c9>>
+
* @lightSyntaxTransform
+
* @nogrep
+
*/
+
+
/* tslint:disable */
+
/* eslint-disable */
+
// @ts-nocheck
+
+
import { ReaderFragment } from 'relay-runtime';
+
import { FragmentRefs } from "relay-runtime";
+
export type TrackItem_play$data = {
+
readonly actorHandle: string | null | undefined;
+
readonly appBskyActorProfile: {
+
readonly displayName: string | null | undefined;
+
} | null | undefined;
+
readonly artists: any | null | undefined;
+
readonly playedTime: string | null | undefined;
+
readonly releaseMbId: string | null | undefined;
+
readonly releaseName: string | null | undefined;
+
readonly trackName: string;
+
readonly " $fragmentType": "TrackItem_play";
+
};
+
export type TrackItem_play$key = {
+
readonly " $data"?: TrackItem_play$data;
+
readonly " $fragmentSpreads": FragmentRefs<"TrackItem_play">;
+
};
+
+
const node: ReaderFragment = {
+
"argumentDefinitions": [],
+
"kind": "Fragment",
+
"metadata": null,
+
"name": "TrackItem_play",
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "trackName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "playedTime",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "artists",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseName",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "releaseMbId",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "actorHandle",
+
"storageKey": null
+
},
+
{
+
"alias": null,
+
"args": null,
+
"concreteType": "AppBskyActorProfile",
+
"kind": "LinkedField",
+
"name": "appBskyActorProfile",
+
"plural": false,
+
"selections": [
+
{
+
"alias": null,
+
"args": null,
+
"kind": "ScalarField",
+
"name": "displayName",
+
"storageKey": null
+
}
+
],
+
"storageKey": null
+
}
+
],
+
"type": "FmTealAlphaFeedPlay",
+
"abstractKey": null
+
};
+
+
(node as any).hash = "08e8e2c14a894471e9a3153f8918e02e";
+
+
export default node;
+1
src/albumArtCache.ts
···
+
export const albumArtCache = new Map<string, string | null>();
+1
src/index.css
···
+
@import "tailwindcss";
+43
src/main.tsx
···
+
import { StrictMode, Suspense } from "react";
+
import { createRoot } from "react-dom/client";
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
+
import "./index.css";
+
import App from "./App.tsx";
+
import Profile from "./Profile.tsx";
+
import LoadingFallback from "./LoadingFallback.tsx";
+
import { RelayEnvironmentProvider } from "react-relay";
+
import { Environment, Network, type FetchFunction } from "relay-runtime";
+
+
const HTTP_ENDPOINT =
+
"https://api.slices.network/graphql?slice=at://did:plc:fpruhuo22xkm5o7ttr2ktxdo/network.slices.slice/3m257yljpbg2a";
+
+
const fetchGraphQL: FetchFunction = async (request, variables) => {
+
const resp = await fetch(HTTP_ENDPOINT, {
+
method: "POST",
+
headers: { "Content-Type": "application/json" },
+
body: JSON.stringify({ query: request.text, variables }),
+
});
+
if (!resp.ok) {
+
throw new Error("Response failed.");
+
}
+
return await resp.json();
+
};
+
+
const environment = new Environment({
+
network: Network.create(fetchGraphQL),
+
});
+
+
createRoot(document.getElementById("root")!).render(
+
<StrictMode>
+
<BrowserRouter>
+
<RelayEnvironmentProvider environment={environment}>
+
<Suspense fallback={<LoadingFallback />}>
+
<Routes>
+
<Route path="/" element={<App />} />
+
<Route path="/profile/:handle" element={<Profile />} />
+
</Routes>
+
</Suspense>
+
</RelayEnvironmentProvider>
+
</BrowserRouter>
+
</StrictMode>
+
);
+48
src/useAlbumArt.ts
···
+
import { useState, useEffect } from "react";
+
import { albumArtCache } from "./albumArtCache";
+
+
export function useAlbumArt(releaseMbId: string | null | undefined) {
+
const [albumArtUrl, setAlbumArtUrl] = useState<string | null>(null);
+
const [isLoading, setIsLoading] = useState(false);
+
+
useEffect(() => {
+
if (!releaseMbId) return;
+
+
// Check cache first
+
if (albumArtCache.has(releaseMbId)) {
+
setAlbumArtUrl(albumArtCache.get(releaseMbId) || null);
+
setIsLoading(false);
+
return;
+
}
+
+
setIsLoading(true);
+
+
const fetchAlbumArt = async () => {
+
try {
+
// Fetch cover art from Cover Art Archive
+
const coverArtUrl = `https://coverartarchive.org/release/${releaseMbId}/front-250`;
+
+
// Check if cover art exists
+
const coverArtResponse = await fetch(coverArtUrl, { method: "HEAD" });
+
+
if (coverArtResponse.ok) {
+
setAlbumArtUrl(coverArtUrl);
+
albumArtCache.set(releaseMbId, coverArtUrl);
+
} else {
+
setAlbumArtUrl(null);
+
albumArtCache.set(releaseMbId, null);
+
}
+
} catch (error) {
+
console.error("Error fetching album art:", error);
+
setAlbumArtUrl(null);
+
albumArtCache.set(releaseMbId, null);
+
} finally {
+
setIsLoading(false);
+
}
+
};
+
+
fetchAlbumArt();
+
}, [releaseMbId]);
+
+
return { albumArtUrl, isLoading };
+
}
+28
tsconfig.app.json
···
+
{
+
"compilerOptions": {
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+
"target": "ES2022",
+
"useDefineForClassFields": true,
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
+
"module": "ESNext",
+
"types": ["vite/client"],
+
"skipLibCheck": true,
+
+
/* Bundler mode */
+
"moduleResolution": "bundler",
+
"allowImportingTsExtensions": true,
+
"verbatimModuleSyntax": true,
+
"moduleDetection": "force",
+
"noEmit": true,
+
"jsx": "react-jsx",
+
+
/* Linting */
+
"strict": true,
+
"noUnusedLocals": true,
+
"noUnusedParameters": true,
+
"erasableSyntaxOnly": true,
+
"noFallthroughCasesInSwitch": true,
+
"noUncheckedSideEffectImports": true
+
},
+
"include": ["src"]
+
}
+7
tsconfig.json
···
+
{
+
"files": [],
+
"references": [
+
{ "path": "./tsconfig.app.json" },
+
{ "path": "./tsconfig.node.json" }
+
]
+
}
+26
tsconfig.node.json
···
+
{
+
"compilerOptions": {
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+
"target": "ES2023",
+
"lib": ["ES2023"],
+
"module": "ESNext",
+
"types": ["node"],
+
"skipLibCheck": true,
+
+
/* Bundler mode */
+
"moduleResolution": "bundler",
+
"allowImportingTsExtensions": true,
+
"verbatimModuleSyntax": true,
+
"moduleDetection": "force",
+
"noEmit": true,
+
+
/* Linting */
+
"strict": true,
+
"noUnusedLocals": true,
+
"noUnusedParameters": true,
+
"erasableSyntaxOnly": true,
+
"noFallthroughCasesInSwitch": true,
+
"noUncheckedSideEffectImports": true
+
},
+
"include": ["vite.config.ts"]
+
}
+7
vite.config.ts
···
+
import { defineConfig } from "vite";
+
import react from "@vitejs/plugin-react";
+
+
// https://vite.dev/config/
+
export default defineConfig({
+
plugins: [react({ babel: { plugins: ["relay"] } })],
+
});