Full Code of kern/filepizza for AI

main 3258673e7901 cached
122 files
295.7 KB
102.5k tokens
142 symbols
1 requests
Download .txt
Showing preview only (323K chars total). Download the full file or copy to clipboard to get everything.
Repository: kern/filepizza
Branch: main
Commit: 3258673e7901
Files: 122
Total size: 295.7 KB

Directory structure:
gitextract_c85d7kb8/

├── .claude/
│   └── settings.local.json
├── .cursorrules
├── .dockerignore
├── .github/
│   └── workflows/
│       ├── main.yml
│       └── tests.yml
├── .gitignore
├── .prettierrc.js
├── CLAUDE.md
├── Dockerfile
├── LICENSE
├── README.md
├── bin/
│   └── peerjs.js
├── docker-compose.production.yml
├── docker-compose.yml
├── docs/
│   └── file-transfer-protocol.md
├── eslint.config.mjs
├── next-env.d.ts
├── next.config.js
├── package.json
├── playwright.config.ts
├── postcss.config.js
├── public/
│   ├── robots.txt
│   ├── stream.html
│   └── sw.js
├── renovate.json
├── scripts/
│   └── pull-and-run.sh
├── src/
│   ├── app/
│   │   ├── api/
│   │   │   ├── create/
│   │   │   │   └── route.ts
│   │   │   ├── destroy/
│   │   │   │   └── route.ts
│   │   │   ├── ice/
│   │   │   │   └── route.ts
│   │   │   └── renew/
│   │   │       └── route.ts
│   │   ├── download/
│   │   │   └── [...slug]/
│   │   │       └── page.tsx
│   │   ├── layout.tsx
│   │   ├── not-found.tsx
│   │   ├── page.tsx
│   │   └── reported/
│   │       └── page.tsx
│   ├── channel.ts
│   ├── components/
│   │   ├── AddFilesButton.tsx
│   │   ├── CancelButton.tsx
│   │   ├── ConnectionListItem.tsx
│   │   ├── CopyableInput.tsx
│   │   ├── DownloadButton.tsx
│   │   ├── Downloader.tsx
│   │   ├── DropZone.tsx
│   │   ├── ErrorMessage.tsx
│   │   ├── Footer.tsx
│   │   ├── InputLabel.tsx
│   │   ├── Loading.tsx
│   │   ├── ModeToggle.tsx
│   │   ├── PasswordField.tsx
│   │   ├── ProgressBar.tsx
│   │   ├── QueryClientProvider.tsx
│   │   ├── ReportTermsViolationButton.tsx
│   │   ├── ReturnHome.tsx
│   │   ├── Spinner.tsx
│   │   ├── StartButton.tsx
│   │   ├── StopButton.tsx
│   │   ├── SubtitleText.tsx
│   │   ├── TermsAcceptance.tsx
│   │   ├── ThemeProvider.tsx
│   │   ├── TitleText.tsx
│   │   ├── TypeBadge.tsx
│   │   ├── UnlockButton.tsx
│   │   ├── UploadFileList.tsx
│   │   ├── Uploader.tsx
│   │   ├── WebRTCProvider.tsx
│   │   └── Wordmark.tsx
│   ├── config.ts
│   ├── coturn.ts
│   ├── fs.ts
│   ├── hooks/
│   │   ├── useClipboard.ts
│   │   ├── useDownloader.ts
│   │   ├── useRotatingSpinner.ts
│   │   ├── useUploaderChannel.ts
│   │   └── useUploaderConnections.ts
│   ├── log.ts
│   ├── messages.ts
│   ├── redisClient.ts
│   ├── routes.ts
│   ├── slugs.ts
│   ├── styles.css
│   ├── toppings.ts
│   ├── types.ts
│   ├── utils/
│   │   ├── download.ts
│   │   └── pluralize.ts
│   └── zip-stream.ts
├── tests/
│   ├── e2e/
│   │   ├── add-files.test.ts
│   │   ├── basic.test.ts
│   │   ├── file-transfer.test.ts
│   │   └── helpers.ts
│   └── unit/
│       ├── CancelButton.test.tsx
│       ├── ConnectionListItem.test.tsx
│       ├── CopyableInput.test.tsx
│       ├── DownloadButton.test.tsx
│       ├── Downloader.subcomponents.test.tsx
│       ├── DropZone.test.tsx
│       ├── ErrorMessage.test.tsx
│       ├── Footer.test.tsx
│       ├── InputLabel.test.tsx
│       ├── Loading.test.tsx
│       ├── ModeToggle.test.tsx
│       ├── PasswordField.test.tsx
│       ├── ProgressBar.test.tsx
│       ├── QueryClientProvider.test.tsx
│       ├── ReportTermsViolationButton.test.tsx
│       ├── ReturnHome.test.tsx
│       ├── Spinner.test.tsx
│       ├── StartButton.test.tsx
│       ├── StopButton.test.tsx
│       ├── TermsAcceptance.test.tsx
│       ├── ThemeProvider.test.tsx
│       ├── TitleText.test.tsx
│       ├── TypeBadge.test.tsx
│       ├── UnlockButton.test.tsx
│       ├── UploadFileList.test.tsx
│       ├── Uploader.test.tsx
│       ├── WebRTCProvider.test.tsx
│       ├── Wordmark.test.tsx
│       ├── isFinalChunk.test.ts
│       └── useRotatingSpinner.test.ts
├── tsconfig.json
├── vitest.config.ts
└── vitest.setup.ts

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

================================================
FILE: .claude/settings.local.json
================================================
{
  "permissions": {
    "allow": [
      "Bash(pnpm build:*)",
      "Bash(pnpm test:*)",
      "Bash(npm test:*)"
    ],
    "deny": []
  }
}

================================================
FILE: .cursorrules
================================================
- Use TypeScript.
- Use function syntax for defining React components. Define the prop types inline.
- If a value is exported, it should be exported on the same line as its definition.
- Always define the return type of a function or component.
- Use Tailwind CSS for styling.
- Don't use trailing semicolons.

================================================
FILE: .dockerignore
================================================
.DS_Store
.next
node_modules
dist
.env

================================================
FILE: .github/workflows/main.yml
================================================
name: main
on:
  push:
    branches:
      - main
jobs:
  build:
    name: Docker build, tag, and push
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Docker build, tag, and push
      uses: pangzineng/Github-Action-One-Click-Docker@master
      env:
        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
        DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}


================================================
FILE: .github/workflows/tests.yml
================================================
name: tests
on: [push]
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install
      - run: pnpm exec playwright install --with-deps
      - run: pnpm lint:check
      - run: pnpm format:check
      - run: pnpm type:check
      - run: pnpm test
      - run: pnpm build
      - run: pnpm test:e2e


================================================
FILE: .gitignore
================================================
.DS_Store
.next
node_modules
dist
tsconfig.tsbuildinfo
.env

================================================
FILE: .prettierrc.js
================================================
'use strict';

module.exports = {
  semi: false,
  trailingComma: 'all',
  singleQuote: true,
  printWidth: 80,
  tabWidth: 2,
};


================================================
FILE: CLAUDE.md
================================================
# FilePizza Development Guide

A peer-to-peer file transfer application built with modern web technologies.

## Prerequisites

- [Node.js](https://nodejs.org/) (v18+)
- [pnpm](https://pnpm.io/) (preferred package manager)

## Quick Start

```bash
git clone https://github.com/kern/filepizza.git
cd filepizza
pnpm install
pnpm dev
```

## Available Commands

### Development
- `pnpm dev` - Start development server
- `pnpm dev:full` - Start with Redis and COTURN for full WebRTC testing

### Building & Testing
- `pnpm build` - Build for production
- `pnpm test` - Run unit tests with Vitest
- `pnpm test:watch` - Run tests in watch mode
- `pnpm test:e2e` - Run E2E tests with Playwright

### Code Quality
- `pnpm lint:check` - Check ESLint rules
- `pnpm lint:fix` - Fix ESLint issues
- `pnpm format` - Format code with Prettier
- `pnpm format:check` - Check code formatting
- `pnpm type:check` - TypeScript type checking

### Docker
- `pnpm docker:build` - Build Docker image
- `pnpm docker:up` - Start containers
- `pnpm docker:down` - Stop containers

### CI Pipeline
- `pnpm ci` - Run full CI pipeline (lint, format, type-check, test, build, e2e, docker)

## Tech Stack

- **Framework**: Next.js 15 with App Router
- **UI**: React 19 + Tailwind CSS v4
- **Language**: TypeScript
- **Testing**: Vitest (unit) + Playwright (E2E)
- **WebRTC**: PeerJS
- **State Management**: TanStack Query
- **Themes**: next-themes with View Transitions
- **Storage**: Redis (optional)

## Project Structure

```
src/
├── app/                    # Next.js App Router pages
├── components/             # React components
├── hooks/                  # Custom React hooks
├── utils/                  # Utility functions
└── types.ts               # TypeScript definitions
```

## Development Tips

### Using pnpm

This project uses pnpm as the package manager. Benefits include:
- Faster installs and smaller disk usage
- Strict dependency resolution
- Built-in workspace support

Always use `pnpm` instead of `npm` or `yarn`:
```bash
pnpm install package-name
pnpm remove package-name
pnpm update
```

### Code Style

- ESLint + TypeScript ESLint for linting
- Prettier for formatting
- Husky + lint-staged for pre-commit hooks
- Prefer TypeScript over JavaScript
- Use kebab-case for files, PascalCase for components

### Testing Strategy

- Unit tests for components and utilities (`tests/unit/`)
- E2E tests for critical user flows (`tests/e2e/`)
- Test files follow `*.test.ts[x]` naming convention

### WebRTC Development

For full WebRTC testing with TURN/STUN:
```bash
pnpm dev:full
```

This starts Redis and COTURN containers for testing peer connections behind NAT.

## Key Dependencies

- `next` - React framework
- `tailwindcss` - CSS framework
- `@tanstack/react-query` - Server state management
- `peerjs` - WebRTC abstraction
- `next-themes` - Theme switching
- `zod` - Schema validation
- `vitest` - Testing framework
- `playwright` - E2E testing

Run `pnpm ci` before submitting PRs to ensure all checks pass.

================================================
FILE: Dockerfile
================================================
# Stage 1: Dependencies
FROM node:lts-alpine AS deps
RUN apk add --no-cache pnpm
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
# Need all dependencies for build
RUN pnpm install --frozen-lockfile

# Stage 2: Builder
FROM node:lts-alpine AS builder
RUN apk add --no-cache pnpm
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Builds standalone output
RUN pnpm build

# Stage 3: Runner
FROM node:lts-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
ENV PORT 3000

# Only copy standalone output - no need for node_modules
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

USER node
EXPOSE 3000
# Uses standalone server
CMD ["node", "server.js"]


================================================
FILE: LICENSE
================================================
Copyright (c) 2015, Alex Kern
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

## SIL OPEN FONT LICENSE for fonts in static/fonts

Version 1.1 - 26 February 2007
PREAMBLE

The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS

"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS

Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION

This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER

THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.


================================================
FILE: README.md
================================================
<a href="https://xkcd.com/949/"><img src="http://imgs.xkcd.com/comics/file_transfer.png" alt="XKCD 949" width="30%" align="right" /></a> <img src="public/images/wordmark.png" alt="FilePizza wordmark" width="50%" /> <h3>Peer-to-peer file transfers in your browser</h3>

*Cooked up by [Alex Kern](https://kern.io) & [Neeraj Baid](https://github.com/neerajbaid) while eating Sliver @ UC Berkeley.*

Using [WebRTC](http://www.webrtc.org), FilePizza eliminates the initial upload step required by other web-based file sharing services. Because data is never stored in an intermediary server, the transfer is fast, private, and secure.

A hosted instance of FilePizza is available at [file.pizza](https://file.pizza).

## What's new with FilePizza v2

* A new UI with dark mode support, now built on modern browser technologies.
* Works on most mobile browsers, including Mobile Safari.
* Transfers are now directly from the uploader to the downloader's browser (WebRTC without WebTorrent) with faster handshakes.
* Uploaders can monitor the progress of the transfer and stop it if they want.
* Better security and safety measures with password protection and reporting.
* Support for uploading multiple files at once, which downloaders receive as a zip file.
* Streaming downloads with a Service Worker.
* Out-of-process storage of server state using Redis.

## Development

```
$ git clone https://github.com/kern/filepizza.git
$ pnpm install
$ pnpm dev
$ pnpm build
$ pnpm start
```

## Running with Docker

```
$ pnpm docker:build
$ pnpm docker:up
$ pnpm docker:down
```

## Stack

* Next.js
* Tailwind
* TypeScript
* React
* PeerJS for WebRTC
* View Transitions
* Redis (optional)

## Configuration

The server can be customized with the following environment variables:

- `REDIS_URL` – Connection string for a Redis instance used to store channel metadata. If not set, FilePizza falls back to in-memory storage.
- `COTURN_ENABLED` – When set to `true`, enables TURN support for connecting peers behind NAT.
- `TURN_HOST` – Hostname or IP address of the TURN server. Defaults to `127.0.0.1`.
- `TURN_REALM` – Realm used when generating TURN credentials. Defaults to `file.pizza`.
- `STUN_SERVER` – STUN server URL to use when `COTURN_ENABLED` is disabled. Defaults to `stun:stun.l.google.com:19302`.
- `PEERJS_HOST` – Hostname or IP address to the self-hosted PeerJS server. Defaults to `0.peerjs.com`.
- `PEERJS_PATH` – Path to self-hosted PeerJS server. Defaults to `/`.

## FAQ

**How are my files sent?** Your files are sent directly from your browser to the downloader's browser. They never pass through our servers. FilePizza uses WebRTC to send files. This requires that the uploader leave their browser window open until the transfer is complete.

**Can multiple people download my file at once?** Yes! Just send them your short or long URL.

**How big can my files be?** As big as your browser can handle.

**What happens when I close my browser?** The URLs for your files will no longer work. If a downloader has completed the transfer, that downloader will continue to seed to incomplete downloaders, but no new downloads may be initiated.

**Are my files encrypted?** Yes, all WebRTC communications are automatically encrypted using public-key cryptography because of DTLS. You can add an optional password to your upload for an extra layer of security.

## License & Acknowledgements

FilePizza is released under the [BSD 3-Clause license](https://github.com/kern/filepizza/blob/main/LICENSE). A huge thanks to [iblowyourdesign](https://dribbble.com/iblowyourdesign) for the pizza illustration.


================================================
FILE: bin/peerjs.js
================================================
#!/usr/bin/env node
const express = require('express')
const { ExpressPeerServer } = require('peer')

const app = express();
const server = app.listen(9000);
const peerServer = ExpressPeerServer(server, {
  path: '/filepizza'
})

app.use('/peerjs', peerServer)


================================================
FILE: docker-compose.production.yml
================================================
services:
  redis:
    image: redis:latest
    ports:
      - 127.0.0.1:6379:6379
    networks:
      - filepizza
    volumes:
      - redis_data:/data
  coturn:
    image: coturn/coturn
    ports:
      - 3478:3478
      - 3478:3478/udp
      - 5349:5349
      - 5349:5349/udp
      - 60000-60128:60000-60128/udp
    environment:
      - DETECT_EXTERNAL_IP=yes
      - DETECT_RELAY_IP=yes
    command: -n --log-file=stdout --redis-userdb="ip=redis connect_timeout=30" --min-port=60000 --max-port=60128
    networks:
      - filepizza
  filepizza:
    build: .
    image: kern/filepizza:latest
    ports:
      - 0.0.0.0:80:80
    environment:
      - PORT=80
      - REDIS_URL=redis://redis:6379
      - COTURN_ENABLED=true
    networks:
      - filepizza
    depends_on:
      - redis
    env_file:
      - .env

networks:
  filepizza:
    driver: bridge

volumes:
  redis_data:


================================================
FILE: docker-compose.yml
================================================
services:
  redis:
    image: redis:latest
    ports:
      - 6379:6379
    networks:
      - filepizza
    volumes:
      - redis_data:/data
  coturn:
    image: coturn/coturn
    ports:
      - 3478:3478
      - 3478:3478/udp
      - 5349:5349
      - 5349:5349/udp
      # Relay Ports
      # - 49152-65535:49152-65535/udp
    environment:
      - DETECT_EXTERNAL_IP=yes
      - DETECT_RELAY_IP=yes
    command: -n --log-file=stdout --redis-userdb="ip=redis connect_timeout=30"
    networks:
      - filepizza
  filepizza:
    build: .
    image: kern/filepizza:latest
    ports:
      - 8080:8080
    environment:
      - PORT=8080
      - REDIS_URL=redis://redis:6379
    networks:
      - filepizza
    depends_on:
      - redis

networks:
  filepizza:
    driver: bridge

volumes:
  redis_data:


================================================
FILE: docs/file-transfer-protocol.md
================================================
# FilePizza File Transfer Protocol

This document explains the message-based protocol that FilePizza uses to
transfer files directly between browsers over a WebRTC data channel.  It
covers the complete conversation required to build either an uploader or a
downloader and includes examples for common scenarios.

## Architecture Overview

```mermaid
flowchart LR
    Uploader -- WebRTC / PeerJS --> Downloader
    Uploader -- REST --> Server[(FilePizza Server)]
    Downloader -- REST --> Server
    Server -- signalling / slug --> Uploader
    Server -- signalling / slug --> Downloader
```

1. The uploader creates a channel with the server and receives a slug that
   encodes its PeerJS identifier.
2. The downloader resolves the slug via the server to obtain the uploader's
   PeerJS identifier.
3. All subsequent messages travel directly between peers over a reliable
   WebRTC data channel.

## Message Types

Every message is a JSON object with a `type` field that matches one of the
values in the table below.  Fields marked with `?` are optional.

```mermaid
classDiagram
    class RequestInfo {
        +"RequestInfo" type
        +string browserName
        +string browserVersion
        +string osName
        +string osVersion
        +string mobileVendor
        +string mobileModel
    }
    class Info {
        +"Info" type
        +FileInfo[] files
    }
    class FileInfo {
        +string fileName
        +number size
        +string type
    }
    class Start {
        +"Start" type
        +string fileName
        +number offset
    }
    class Chunk {
        +"Chunk" type
        +string fileName
        +number offset
        +ArrayBuffer bytes
        +boolean final
    }
    class ChunkAck {
        +"ChunkAck" type
        +string fileName
        +number offset
        +number bytesReceived
    }
    class Pause {
        +"Pause" type
    }
    class Done {
        +"Done" type
    }
    class Error {
        +"Error" type
        +string error
    }
    class PasswordRequired {
        +"PasswordRequired" type
        +string errorMessage?
    }
    class UsePassword {
        +"UsePassword" type
        +string password
    }
    class Report {
        +"Report" type
    }
```

Chunks are sent in pieces of at most 256 KiB (`MAX_CHUNK_SIZE`). The `final` flag in a `Chunk` message marks the last piece of a file.

## Normal Transfer Sequence

The following diagram shows the exchange for downloading multiple files
without a password.

```mermaid
sequenceDiagram
    participant D as Downloader
    participant U as Uploader
    D->>U: RequestInfo
    U-->>D: Info(files)
    loop For each file
        D->>U: Start(fileName, offset=0)
        loop For each chunk
            U-->>D: Chunk(offset, bytes, final=false)
            D->>U: ChunkAck(offset, bytesReceived)
        end
        U-->>D: Chunk(offset, bytes, final=true)
        D->>U: ChunkAck(offset, bytesReceived)
    end
    D->>U: Done
    U-->>D: close connection
```

## Password‑Protected Transfers

If the uploader specified a password when creating the channel, the
conversation includes an authentication step.

```mermaid
sequenceDiagram
    participant D as Downloader
    participant U as Uploader
    D->>U: RequestInfo
    U-->>D: PasswordRequired(errorMessage?)
    D->>U: UsePassword(password)
    U-->>D: Info(files) or PasswordRequired("Invalid password")
    Note over D,U: Continue with normal transfer sequence on success
```

## Pause and Resume

A downloader may pause an in‑progress transfer.  To resume, it reconnects and
requests the remainder of the file starting at the last acknowledged offset.

```mermaid
sequenceDiagram
    participant D as Downloader
    participant U as Uploader
    D->>U: Start(fileName, offset=0)
    U-->>D: Chunk(...)
    D->>U: ChunkAck(...)
    D->>U: Pause
    Note over D,U: Connection closed or kept idle
    D->>U: Start(fileName, offset=previouslyAcked)
    Note over D,U: Transfer resumes from offset
```

## Reporting

A special PeerJS connection with metadata `{ type: "report" }` causes the
uploader to broadcast a `Report` message to all connected downloaders and to
redirect its own UI to a reported page.  Downloaders receiving this message
should abort the transfer.

```mermaid
sequenceDiagram
    participant Reporter
    participant U as Uploader
    participant D as Downloader
    Reporter->>U: Peer connection(type="report")
    U-->>D: Report
    U-->>Reporter: redirect to /reported
```

## Example Conversations

### Single file without password

```
RequestInfo
Info [{ fileName: "photo.jpg", size: 1048576, type: "image/jpeg" }]
Start { fileName: "photo.jpg", offset: 0 }
Chunk { offset: 0, bytes: <256 KB>, final: false }
ChunkAck { offset: 0, bytesReceived: 262144 }
...
Chunk { offset: 1048576, bytes: <0>, final: true }
ChunkAck { offset: 1048576, bytesReceived: 0 }
Done
```

### Password‑protected download

```
RequestInfo
PasswordRequired
UsePassword { password: "secret" }
Info [...]
...
```

### Resuming after interruption

```
RequestInfo
Info [...]
Start { fileName: "video.mp4", offset: 0 }
Chunk/ChunkAck exchanges...
<connection drops after 1 MB>
Start { fileName: "video.mp4", offset: 1048576 }
Chunk/ChunkAck exchanges...
Done
```

---

With these message definitions and sequences you can implement a compatible
uploader or downloader for FilePizza or adapt the protocol for other
applications.


================================================
FILE: eslint.config.mjs
================================================
// @ts-check

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config({
  extends: [
    eslint.configs.recommended,
    tseslint.configs.recommended,
  ],
  rules: {
    '@typescript-eslint/no-unused-vars': [
      'error',
      { argsIgnorePattern: '^_' },
    ],
    '@typescript-eslint/no-use-before-define': [
      'error',
      { variables: false },
    ],
    '@typescript-eslint/promise-function-async': 'off',
    '@typescript-eslint/require-await': 'off',
    '@typescript-eslint/no-explicit-any': 'warn',
    'import/no-unused-modules': 'off',
    'import/group-exports': 'off',
    'import/no-extraneous-dependencies': 'off',
    'new-cap': 'off',
    'no-inline-comments': 'off',
    'no-shadow': 'warn',
    'no-use-before-define': 'off',
  },
  files: ['src/**/*.ts[x]'],
  ignores: ['legacy', 'node_modules', '.next'],
});


================================================
FILE: next-env.d.ts
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />

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


================================================
FILE: next.config.js
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Disable strict mode to avoid calling useEffect twice in development.
  // The uploader and downloader are both using useEffect to listen for peerjs events
  // which causes the connection to be created twice.
  reactStrictMode: false,
  output: 'standalone'
}

module.exports = nextConfig

================================================
FILE: package.json
================================================
{
  "name": "filepizza",
  "version": "2.0.0",
  "description": "Free peer-to-peer file transfers in your browser.",
  "author": "Alex Kern <alex@kern.io> (http://kern.io)",
  "license": "BSD-3-Clause",
  "homepage": "https://github.com/kern/filepizza",
  "scripts": {
    "dev": "next",
    "dev:full": "docker compose up redis coturn -d && COTURN_ENABLED=true REDIS_URL=redis://localhost:6379 next",
    "build": "next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/",
    "start": "next start",
    "start:peerjs": "./bin/peerjs.js",
    "lint:check": "eslint 'src/**/*.ts[x]'",
    "lint:fix": "eslint 'src/**/*.ts[x]' --fix",
    "docker:build": "docker compose build",
    "docker:up": "docker compose up -d",
    "docker:down": "docker compose down",
    "docker:logs": "docker compose logs -f",
    "docker:ps": "docker compose ps",
    "docker:restart": "docker compose restart",
    "docker:clean": "docker compose down -v --rmi all",
    "format": "prettier --write \"src/**/*.{ts,tsx}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
    "type:check": "tsc --noEmit",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:e2e": "playwright test",
    "ci": "pnpm lint:check && pnpm format:check && pnpm type:check && pnpm test && pnpm build && pnpm test:e2e && pnpm docker:build"
  },
  "repository": {
    "type": "git",
    "url": "git@github.com:kern/filepizza.git"
  },
  "bugs": {
    "url": "https://github.com/kern/filepizza/issues"
  },
  "dependencies": {
    "@tailwindcss/postcss": "^4.1.11",
    "@tanstack/react-query": "^5.55.2",
    "autoprefixer": "^10.4.20",
    "debug": "^4.3.6",
    "express": "^5.0.0",
    "ioredis": "^5.4.2",
    "next": "~15.5.11",
    "next-themes": "^0.4.4",
    "next-view-transitions": "^0.3.4",
    "nodemon": "^3.0.0",
    "peer": "^1.0.0",
    "peerjs": "^1.5.4",
    "postcss": "^8.4.44",
    "react": "~19.2.3",
    "react-device-detect": "^2.0.0",
    "react-dom": "~19.2.3",
    "react-qr-code": "^2.0.15",
    "streamsaver": "^2.0.6",
    "tailwindcss": "^4.1.11",
    "web-streams-polyfill": "^4.0.0",
    "webrtcsupport": "^2.2.0",
    "zod": "^4.0.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.30.0",
    "@playwright/test": "^1.53.2",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.3.0",
    "@testing-library/user-event": "^14.6.1",
    "@types/debug": "^4.1.12",
    "@types/node": "^22.10.2",
    "@types/react": "^19.0.2",
    "@typescript-eslint/eslint-plugin": "^8.18.2",
    "@typescript-eslint/parser": "^8.18.2",
    "@vitejs/plugin-react": "^4.6.0",
    "@vitest/coverage-v8": "^3.2.4",
    "eslint": "^9.17.0",
    "eslint-config-next": "~15.5.11",
    "eslint-plugin-import": "^2.31.0",
    "eslint-plugin-react": "^7.37.3",
    "husky": "^9.0.0",
    "jsdom": "^26.1.0",
    "lint-staged": "^16.0.0",
    "playwright": "^1.53.2",
    "prettier": "^3.0.0",
    "typescript": "^5.0.0",
    "typescript-eslint": "^8.18.2",
    "vitest": "^3.2.4"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "git add"
    ]
  }
}


================================================
FILE: playwright.config.ts
================================================
import { defineConfig } from '@playwright/test'

export default defineConfig({
  testDir: './tests/e2e',
  workers: 1, // Run tests serially to avoid WebRTC port conflicts
  webServer: {
    command: 'node .next/standalone/server.js',
    url: 'http://localhost:3000',
    timeout: 120 * 1000,
    reuseExistingServer: true,
  },
  use: {
    baseURL: 'http://localhost:3000',
  },
})


================================================
FILE: postcss.config.js
================================================
module.exports = {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {},
  },
}


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


================================================
FILE: public/stream.html
================================================
<!--
  https://github.com/jimmywarting/StreamSaver.js/blob/master/mitm.html

	mitm.html is the lite "man in the middle"

	This is only meant to signal the opener's messageChannel to
	the service worker - when that is done this mitm can be closed
    but it's better to keep it alive since this also stops the sw
    from restarting

	The service worker is capable of intercepting all request and fork their
	own "fake" response - wish we are going to craft
	when the worker then receives a stream then the worker will tell the opener
	to open up a link that will start the download
-->
<script>
// This will prevent the sw from restarting
let keepAlive = () => {
  keepAlive = () => {}
  var ping = location.href.substr(0, location.href.lastIndexOf('/')) + '/ping'
  var interval = setInterval(() => {
    if (sw) {
      sw.postMessage('ping')
    } else {
      fetch(ping).then(res => res.text(!res.ok && clearInterval(interval)))
    }
  }, 10000)
}

// message event is the first thing we need to setup a listner for
// don't want the opener to do a random timeout - instead they can listen for
// the ready event
// but since we need to wait for the Service Worker registration, we store the
// message for later
let messages = []
window.onmessage = evt => messages.push(evt)

let sw = null
let scope = ''

function registerWorker() {
  return navigator.serviceWorker.getRegistration('./').then(swReg => {
    return swReg || navigator.serviceWorker.register('sw.js', { scope: './' })
  }).then(swReg => {
    const swRegTmp = swReg.installing || swReg.waiting

    scope = swReg.scope

    return (sw = swReg.active) || new Promise(resolve => {
      swRegTmp.addEventListener('statechange', fn = () => {
        if (swRegTmp.state === 'activated') {
          swRegTmp.removeEventListener('statechange', fn)
          sw = swReg.active
          resolve()
        }
      })
    })
  })
}

// Now that we have the Service Worker registered we can process messages
function onMessage (event) {
  let { data, ports, origin } = event

  // It's important to have a messageChannel, don't want to interfere
  // with other simultaneous downloads
  if (!ports || !ports.length) {
    throw new TypeError("[StreamSaver] You didn't send a messageChannel")
  }

  if (typeof data !== 'object') {
    throw new TypeError("[StreamSaver] You didn't send a object")
  }

  // the default public service worker for StreamSaver is shared among others.
  // so all download links needs to be prefixed to avoid any other conflict
  data.origin = origin

  // if we ever (in some feature versoin of streamsaver) would like to
  // redirect back to the page of who initiated a http request
  data.referrer = data.referrer || document.referrer || origin

  // pass along version for possible backwards compatibility in sw.js
  data.streamSaverVersion = new URLSearchParams(location.search).get('version')

  if (data.streamSaverVersion === '1.2.0') {
    console.warn('[StreamSaver] please update streamsaver')
  }

  /** @since v2.0.0 */
  if (!data.headers) {
    console.warn("[StreamSaver] pass `data.headers` that you would like to pass along to the service worker\nit should be a 2D array or a key/val object that fetch's Headers api accepts")
  } else {
    // test if it's correct
    // should thorw a typeError if not
    new Headers(data.headers)
  }

  /** @since v2.0.0 */
  if (typeof data.filename === 'string') {
    console.warn("[StreamSaver] You shouldn't send `data.filename` anymore. It should be included in the Content-Disposition header option")
    // Do what File constructor do with fileNames
    data.filename = data.filename.replace(/\//g, ':')
  }

  /** @since v2.0.0 */
  if (data.size) {
    console.warn("[StreamSaver] You shouldn't send `data.size` anymore. It should be included in the content-length header option")
  }

  /** @since v2.0.0 */
  if (data.readableStream) {
    console.warn("[StreamSaver] You should send the readableStream in the messageChannel, not throught mitm")
  }

  /** @since v2.0.0 */
  if (!data.pathname) {
    console.warn("[StreamSaver] Please send `data.pathname` (eg: /pictures/summer.jpg)")
    data.pathname = Math.random().toString().slice(-6) + '/' + data.filename
  }

  // remove all leading slashes
  data.pathname = data.pathname.replace(/^\/+/g, '')

  // remove protocol
  let org = origin.replace(/(^\w+:|^)\/\//, '')

  // set the absolute pathname to the download url.
  data.url = new URL(`${scope + org}/${data.pathname}`).toString()

  if (!data.url.startsWith(`${scope + org}/`)) {
    throw new TypeError('[StreamSaver] bad `data.pathname`')
  }

  // This sends the message data as well as transferring
  // messageChannel.port2 to the service worker. The service worker can
  // then use the transferred port to reply via postMessage(), which
  // will in turn trigger the onmessage handler on messageChannel.port1.

  const transferable = data.readableStream
    ? [ ports[0], data.readableStream ]
    : [ ports[0] ]

  if (!(data.readableStream || data.transferringReadable)) {
    keepAlive()
  }

  return sw.postMessage(data, transferable)
}

if (window.opener) {
  // The opener can't listen to onload event, so we need to help em out!
  // (telling them that we are ready to accept postMessage's)
  window.opener.postMessage('StreamSaver::loadedPopup', '*')
}

if (navigator.serviceWorker) {
  registerWorker().then(() => {
    window.onmessage = onMessage
    messages.forEach(window.onmessage)
  })
} else {
  // FF can ping sw with fetch from a secure hidden iframe
  // shouldn't really be possible?
  keepAlive()
}

</script>


================================================
FILE: public/sw.js
================================================
// https://github.com/jimmywarting/StreamSaver.js/blob/master/sw.js

/* global self ReadableStream Response */

self.addEventListener('install', () => {
  self.skipWaiting()
})

self.addEventListener('activate', (event) => {
  event.waitUntil(self.clients.claim())
})

const map = new Map()

// This should be called once per download
// Each event has a dataChannel that the data will be piped through
self.onmessage = (event) => {
  // We send a heartbeat every x secound to keep the
  // service worker alive if a transferable stream is not sent
  if (event.data === 'ping') {
    return
  }

  const data = event.data
  const downloadUrl =
    data.url ||
    self.registration.scope +
      Math.random() +
      '/' +
      (typeof data === 'string' ? data : data.filename)
  const port = event.ports[0]
  const metadata = new Array(3) // [stream, data, port]

  metadata[1] = data
  metadata[2] = port

  // Note to self:
  // old streamsaver v1.2.0 might still use `readableStream`...
  // but v2.0.0 will always transfer the stream throught MessageChannel #94
  if (event.data.readableStream) {
    metadata[0] = event.data.readableStream
  } else if (event.data.transferringReadable) {
    port.onmessage = (evt) => {
      port.onmessage = null
      metadata[0] = evt.data.readableStream
    }
  } else {
    metadata[0] = createStream(port)
  }

  map.set(downloadUrl, metadata)
  port.postMessage({ download: downloadUrl })
}

function createStream(port) {
  // ReadableStream is only supported by chrome 52
  return new ReadableStream({
    start(controller) {
      // When we receive data on the messageChannel, we write
      port.onmessage = ({ data }) => {
        if (data === 'end') {
          return controller.close()
        }

        if (data === 'abort') {
          controller.error('Aborted the download')
          return
        }

        controller.enqueue(data)
      }
    },
    cancel() {
      console.log('user aborted')
    },
  })
}

self.onfetch = (event) => {
  const url = event.request.url

  // this only works for Firefox
  if (url.endsWith('/ping')) {
    return event.respondWith(new Response('pong'))
  }

  const hijacke = map.get(url)

  if (!hijacke) return null

  const [stream, data, port] = hijacke

  map.delete(url)

  // Not comfortable letting any user control all headers
  // so we only copy over the length & disposition
  const responseHeaders = new Headers({
    'Content-Type': 'application/octet-stream; charset=utf-8',

    // To be on the safe side, The link can be opened in a iframe.
    // but octet-stream should stop it.
    'Content-Security-Policy': "default-src 'none'",
    'X-Content-Security-Policy': "default-src 'none'",
    'X-WebKit-CSP': "default-src 'none'",
    'X-XSS-Protection': '1; mode=block',
  })

  let headers = new Headers(data.headers || {})

  if (headers.has('Content-Length')) {
    responseHeaders.set('Content-Length', headers.get('Content-Length'))
  }

  if (headers.has('Content-Disposition')) {
    responseHeaders.set(
      'Content-Disposition',
      headers.get('Content-Disposition'),
    )
  }

  // data, data.filename and size should not be used anymore
  if (data.size) {
    console.warn('Depricated')
    responseHeaders.set('Content-Length', data.size)
  }

  let fileName = typeof data === 'string' ? data : data.filename
  if (fileName) {
    console.warn('Depricated')
    // Make filename RFC5987 compatible
    fileName = encodeURIComponent(fileName)
      .replace(/['()]/g, escape)
      .replace(/\*/g, '%2A')
    responseHeaders.set(
      'Content-Disposition',
      "attachment; filename*=UTF-8''" + fileName,
    )
  }

  event.respondWith(new Response(stream, { headers: responseHeaders }))

  port.postMessage({ debug: 'Download started' })
}


================================================
FILE: renovate.json
================================================
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended"
  ]
}


================================================
FILE: scripts/pull-and-run.sh
================================================
#!/bin/bash

set -euo pipefail

git pull origin main
sudo docker pull kern/filepizza:latest
sudo docker compose -f docker-compose.production.yml up -d
sudo docker compose logs -f

================================================
FILE: src/app/api/create/route.ts
================================================
import { NextResponse } from 'next/server'
import { getOrCreateChannelRepo } from '../../../channel'

export async function POST(request: Request): Promise<NextResponse> {
  const { uploaderPeerID } = await request.json()

  if (!uploaderPeerID) {
    return NextResponse.json(
      { error: 'Uploader peer ID is required' },
      { status: 400 },
    )
  }

  const channel = await getOrCreateChannelRepo().createChannel(uploaderPeerID)
  return NextResponse.json(channel)
}


================================================
FILE: src/app/api/destroy/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server'
import { getOrCreateChannelRepo } from '../../../channel'

export async function POST(request: NextRequest): Promise<NextResponse> {
  const { slug } = await request.json()

  if (!slug) {
    return NextResponse.json({ error: 'Slug is required' }, { status: 400 })
  }

  // Anyone can destroy a channel if they know the slug. This enables a terms violation reporter to destroy the channel after they report it.

  try {
    await getOrCreateChannelRepo().destroyChannel(slug)
    return NextResponse.json({ success: true }, { status: 200 })
  } catch (error) {
    console.error(error)
    return NextResponse.json(
      { error: 'Failed to destroy channel' },
      { status: 500 },
    )
  }
}


================================================
FILE: src/app/api/ice/route.ts
================================================
import { NextResponse } from 'next/server'
import crypto from 'crypto'
import { setTurnCredentials } from '../../../coturn'

const turnHost = process.env.TURN_HOST || '127.0.0.1'
const stunServer = process.env.STUN_SERVER || 'stun:stun.l.google.com:19302'
const peerjsHost = process.env.PEERJS_HOST || '0.peerjs.com'
const peerjsPath = process.env.PEERJS_PATH || '/'

export async function POST(): Promise<NextResponse> {
  if (!process.env.COTURN_ENABLED) {
    return NextResponse.json({
      host: peerjsHost,
      path: peerjsPath,
      iceServers: [{ urls: stunServer }],
    })
  }

  // Generate ephemeral credentials
  const username = crypto.randomBytes(8).toString('hex')
  const password = crypto.randomBytes(8).toString('hex')
  const ttl = 86400 // 24 hours

  // Store credentials in Redis
  await setTurnCredentials(username, password, ttl)

  return NextResponse.json({
    host: peerjsHost,
    path: peerjsPath,
    iceServers: [
      { urls: stunServer },
      {
        urls: [`turn:${turnHost}:3478`, `turns:${turnHost}:5349`],
        username,
        credential: password,
      },
    ],
  })
}


================================================
FILE: src/app/api/renew/route.ts
================================================
import { NextRequest, NextResponse } from 'next/server'
import { getOrCreateChannelRepo } from '../../../channel'

export async function POST(request: NextRequest): Promise<NextResponse> {
  const { slug, secret } = await request.json()

  if (!slug) {
    return NextResponse.json({ error: 'Slug is required' }, { status: 400 })
  }

  if (!secret) {
    return NextResponse.json({ error: 'Secret is required' }, { status: 400 })
  }

  const success = await getOrCreateChannelRepo().renewChannel(slug, secret)
  return NextResponse.json({ success })
}


================================================
FILE: src/app/download/[...slug]/page.tsx
================================================
import { JSX } from 'react'
import { notFound } from 'next/navigation'
import { getOrCreateChannelRepo } from '../../../channel'
import Spinner from '../../../components/Spinner'
import Wordmark from '../../../components/Wordmark'
import Downloader from '../../../components/Downloader'
import WebRTCPeerProvider from '../../../components/WebRTCProvider'
import ReportTermsViolationButton from '../../../components/ReportTermsViolationButton'

const normalizeSlug = (rawSlug: string | string[]): string => {
  if (typeof rawSlug === 'string') {
    return rawSlug
  } else {
    return rawSlug.join('/')
  }
}

export default async function DownloadPage({
  params,
}: {
  params: Promise<{ slug: string[] }>
}): Promise<JSX.Element> {
  const { slug: slugRaw } = await params
  const slug = normalizeSlug(slugRaw)
  const channel = await getOrCreateChannelRepo().fetchChannel(slug)

  if (!channel) {
    notFound()
  }

  return (
    <div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
      <Spinner direction="down" />
      <Wordmark />
      <WebRTCPeerProvider>
        <Downloader uploaderPeerID={channel.uploaderPeerID} />
        <ReportTermsViolationButton
          uploaderPeerID={channel.uploaderPeerID}
          slug={slug}
        />
      </WebRTCPeerProvider>
    </div>
  )
}


================================================
FILE: src/app/layout.tsx
================================================
import React from 'react'
import Footer from '../components/Footer'
import '../styles.css'
import { ThemeProvider } from '../components/ThemeProvider'
import { ModeToggle } from '../components/ModeToggle'
import FilePizzaQueryClientProvider from '../components/QueryClientProvider'
import { Viewport } from 'next'
import { ViewTransitions } from 'next-view-transitions'

export const metadata = {
  title: 'FilePizza • Your files, delivered.',
  description: 'Peer-to-peer file transfers in your web browser.',
  charSet: 'utf-8',
  openGraph: {
    url: 'https://file.pizza',
    title: 'FilePizza • Your files, delivered.',
    description: 'Peer-to-peer file transfers in your web browser.',
    images: [{ url: 'https://file.pizza/images/fb.png' }],
  },
}

export const viewport: Viewport = {
  width: 'device-width',
  initialScale: 1,
  maximumScale: 1,
  userScalable: false,
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}): React.ReactElement {
  return (
    <ViewTransitions>
      <html lang="en" suppressHydrationWarning>
        <body>
          <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
            <FilePizzaQueryClientProvider>
              <main>{children}</main>
              <Footer />
              <ModeToggle />
            </FilePizzaQueryClientProvider>
          </ThemeProvider>
        </body>
      </html>
    </ViewTransitions>
  )
}


================================================
FILE: src/app/not-found.tsx
================================================
import { JSX } from 'react'
import Spinner from '../components/Spinner'
import Wordmark from '../components/Wordmark'
import ReturnHome from '../components/ReturnHome'
import TitleText from '../components/TitleText'

export const metadata = {
  title: 'FilePizza - 404: Slice Not Found',
  description: 'Oops! This slice of FilePizza seems to be missing.',
}

export default async function NotFound(): Promise<JSX.Element> {
  return (
    <div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
      <Spinner direction="down" />
      <Wordmark />
      <TitleText>404: Looks like this slice of FilePizza got eaten!</TitleText>
      <ReturnHome />
    </div>
  )
}


================================================
FILE: src/app/page.tsx
================================================
'use client'

import React, { JSX, useCallback, useState } from 'react'
import WebRTCPeerProvider from '../components/WebRTCProvider'
import DropZone from '../components/DropZone'
import UploadFileList from '../components/UploadFileList'
import Uploader from '../components/Uploader'
import PasswordField from '../components/PasswordField'
import StartButton from '../components/StartButton'
import { UploadedFile } from '../types'
import Spinner from '../components/Spinner'
import Wordmark from '../components/Wordmark'
import CancelButton from '../components/CancelButton'
import { useMemo } from 'react'
import { getFileName } from '../fs'
import TitleText from '../components/TitleText'
import SubtitleText from '../components/SubtitleText'
import { pluralize } from '../utils/pluralize'
import TermsAcceptance from '../components/TermsAcceptance'
import AddFilesButton from '../components/AddFilesButton'

function PageWrapper({ children }: { children: React.ReactNode }): JSX.Element {
  return (
    <div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto px-4">
      <Spinner direction="up" />
      <Wordmark />
      {children}
    </div>
  )
}

function InitialState({
  onDrop,
}: {
  onDrop: (files: UploadedFile[]) => void
}): JSX.Element {
  return (
    <PageWrapper>
      <div className="flex flex-col items-center space-y-1 max-w-md">
        <TitleText>Peer-to-peer file transfers in your browser.</TitleText>
      </div>
      <DropZone onDrop={onDrop} />
      <TermsAcceptance />
    </PageWrapper>
  )
}

function useUploaderFileListData(uploadedFiles: UploadedFile[]) {
  return useMemo(() => {
    return uploadedFiles.map((item) => ({
      fileName: getFileName(item),
      type: item.type,
    }))
  }, [uploadedFiles])
}

function ConfirmUploadState({
  uploadedFiles,
  password,
  onChangePassword,
  onCancel,
  onStart,
  onRemoveFile,
  onAddFiles,
}: {
  uploadedFiles: UploadedFile[]
  password: string
  onChangePassword: (pw: string) => void
  onCancel: () => void
  onStart: () => void
  onRemoveFile: (index: number) => void
  onAddFiles: (files: UploadedFile[]) => void
}): JSX.Element {
  const fileListData = useUploaderFileListData(uploadedFiles)
  return (
    <PageWrapper>
      <TitleText>
        You are about to start uploading{' '}
        {pluralize(uploadedFiles.length, 'file', 'files')}.{' '}
        <AddFilesButton onAdd={onAddFiles} />
      </TitleText>
      <UploadFileList files={fileListData} onRemove={onRemoveFile} />
      <PasswordField value={password} onChange={onChangePassword} />
      <div className="flex space-x-4">
        <CancelButton onClick={onCancel} />
        <StartButton onClick={onStart} />
      </div>
    </PageWrapper>
  )
}

function UploadingState({
  uploadedFiles,
  password,
  onStop,
}: {
  uploadedFiles: UploadedFile[]
  password: string
  onStop: () => void
}): JSX.Element {
  const fileListData = useUploaderFileListData(uploadedFiles)
  return (
    <PageWrapper>
      <TitleText>
        You are uploading {pluralize(uploadedFiles.length, 'file', 'files')}.
      </TitleText>
      <SubtitleText>
        Leave this tab open. FilePizza does not store files.
      </SubtitleText>
      <UploadFileList files={fileListData} />
      <WebRTCPeerProvider>
        <Uploader files={uploadedFiles} password={password} onStop={onStop} />
      </WebRTCPeerProvider>
    </PageWrapper>
  )
}

export default function UploadPage(): JSX.Element {
  const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([])
  const [password, setPassword] = useState('')
  const [uploading, setUploading] = useState(false)

  const handleDrop = useCallback((files: UploadedFile[]): void => {
    setUploadedFiles(files)
  }, [])

  const handleChangePassword = useCallback((pw: string) => {
    setPassword(pw)
  }, [])

  const handleStart = useCallback(() => {
    setUploading(true)
  }, [])

  const handleStop = useCallback(() => {
    setUploading(false)
  }, [])

  const handleCancel = useCallback(() => {
    setUploadedFiles([])
    setUploading(false)
  }, [])

  const handleRemoveFile = useCallback((index: number) => {
    setUploadedFiles((fs) => fs.filter((_, i) => i !== index))
  }, [])

  const handleAddFiles = useCallback((files: UploadedFile[]) => {
    setUploadedFiles((fs) => [...fs, ...files])
  }, [])

  if (!uploadedFiles.length) {
    return <InitialState onDrop={handleDrop} />
  }

  if (!uploading) {
    return (
      <ConfirmUploadState
        uploadedFiles={uploadedFiles}
        password={password}
        onChangePassword={handleChangePassword}
        onCancel={handleCancel}
        onStart={handleStart}
        onRemoveFile={handleRemoveFile}
        onAddFiles={handleAddFiles}
      />
    )
  }

  return (
    <UploadingState
      uploadedFiles={uploadedFiles}
      password={password}
      onStop={handleStop}
    />
  )
}


================================================
FILE: src/app/reported/page.tsx
================================================
import { JSX } from 'react'
import Spinner from '../../components/Spinner'
import Wordmark from '../../components/Wordmark'
import TitleText from '../../components/TitleText'
import ReturnHome from '../../components/ReturnHome'

export default function ReportedPage(): JSX.Element {
  return (
    <div className="flex flex-col items-center space-y-5 py-10 max-w-md mx-auto">
      <Spinner direction="down" />
      <Wordmark />

      <TitleText>This delivery has been halted.</TitleText>
      <div className="px-8 py-6 bg-stone-100 dark:bg-stone-800 rounded-lg border border-stone-200 dark:border-stone-700">
        <h3 className="text-lg font-medium text-stone-800 dark:text-stone-200 mb-4">
          Message from the management
        </h3>
        <p className="text-sm text-stone-600 dark:text-stone-300 leading-relaxed mb-6">
          Just like a pizza with questionable toppings, we've had to put this
          delivery on hold for potential violations of our terms of service. Our
          delivery quality team is looking into it to ensure we maintain our
          high standards.
        </p>
        <div className="text-sm text-stone-500 dark:text-stone-400 italic">
          - The FilePizza Team
        </div>
      </div>

      <ReturnHome />
    </div>
  )
}


================================================
FILE: src/channel.ts
================================================
import 'server-only'
import config from './config'
import { Redis, getRedisClient } from './redisClient'
import { generateShortSlug, generateLongSlug } from './slugs'
import crypto from 'crypto'
import { z } from 'zod'

export type Channel = {
  secret?: string
  longSlug: string
  shortSlug: string
  uploaderPeerID: string
}

const ChannelSchema = z.object({
  secret: z.string().optional(),
  longSlug: z.string(),
  shortSlug: z.string(),
  uploaderPeerID: z.string(),
})

export interface ChannelRepo {
  createChannel(uploaderPeerID: string, ttl?: number): Promise<Channel>
  fetchChannel(slug: string): Promise<Channel | null>
  renewChannel(slug: string, secret: string, ttl?: number): Promise<boolean>
  destroyChannel(slug: string): Promise<void>
}

function getShortSlugKey(shortSlug: string): string {
  return `short:${shortSlug}`
}

function getLongSlugKey(longSlug: string): string {
  return `long:${longSlug}`
}

async function generateShortSlugUntilUnique(
  checkExists: (key: string) => Promise<boolean>,
): Promise<string> {
  for (let i = 0; i < config.shortSlug.maxAttempts; i++) {
    const slug = await generateShortSlug()
    const exists = await checkExists(getShortSlugKey(slug))
    if (!exists) {
      return slug
    }
  }

  throw new Error('max attempts reached generating short slug')
}

async function generateLongSlugUntilUnique(
  checkExists: (key: string) => Promise<boolean>,
): Promise<string> {
  for (let i = 0; i < config.longSlug.maxAttempts; i++) {
    const slug = await generateLongSlug()
    const exists = await checkExists(getLongSlugKey(slug))
    if (!exists) {
      return slug
    }
  }

  throw new Error('max attempts reached generating long slug')
}

function serializeChannel(channel: Channel): string {
  return JSON.stringify(channel)
}

function deserializeChannel(str: string, scrubSecret = false): Channel {
  const parsedChannel = JSON.parse(str)
  const validatedChannel = ChannelSchema.parse(parsedChannel)
  if (scrubSecret) {
    return { ...validatedChannel, secret: undefined }
  }
  return validatedChannel
}

type MemoryStoredChannel = {
  channel: Channel
  expiresAt: number
}

export class MemoryChannelRepo implements ChannelRepo {
  private channels: Map<string, MemoryStoredChannel> = new Map()
  private timeouts: Map<string, NodeJS.Timeout> = new Map()

  private setChannelTimeout(slug: string, ttl: number) {
    // Clear any existing timeout
    const existingTimeout = this.timeouts.get(slug)
    if (existingTimeout) {
      clearTimeout(existingTimeout)
    }

    // Set new timeout to remove channel when expired
    const timeout = setTimeout(() => {
      this.channels.delete(slug)
      this.timeouts.delete(slug)
    }, ttl * 1000)

    this.timeouts.set(slug, timeout)
  }

  async createChannel(
    uploaderPeerID: string,
    ttl: number = config.channel.ttl,
  ): Promise<Channel> {
    const shortSlug = await generateShortSlugUntilUnique(async (key) =>
      this.channels.has(key),
    )
    const longSlug = await generateLongSlugUntilUnique(async (key) =>
      this.channels.has(key),
    )

    const channel: Channel = {
      secret: crypto.randomUUID(),
      longSlug,
      shortSlug,
      uploaderPeerID,
    }

    const expiresAt = Date.now() + ttl * 1000
    const storedChannel = { channel, expiresAt }

    const shortKey = getShortSlugKey(shortSlug)
    const longKey = getLongSlugKey(longSlug)

    this.channels.set(shortKey, storedChannel)
    this.channels.set(longKey, storedChannel)

    this.setChannelTimeout(shortKey, ttl)
    this.setChannelTimeout(longKey, ttl)

    return channel
  }

  async fetchChannel(
    slug: string,
    scrubSecret = false,
  ): Promise<Channel | null> {
    const shortKey = getShortSlugKey(slug)
    const shortChannel = this.channels.get(shortKey)
    if (shortChannel) {
      return scrubSecret
        ? { ...shortChannel.channel, secret: undefined }
        : shortChannel.channel
    }

    const longKey = getLongSlugKey(slug)
    const longChannel = this.channels.get(longKey)
    if (longChannel) {
      return scrubSecret
        ? { ...longChannel.channel, secret: undefined }
        : longChannel.channel
    }

    return null
  }

  async renewChannel(
    slug: string,
    secret: string,
    ttl: number = config.channel.ttl,
  ): Promise<boolean> {
    const channel = await this.fetchChannel(slug)
    if (!channel || channel.secret !== secret) {
      return false
    }

    const expiresAt = Date.now() + ttl * 1000
    const storedChannel = { channel, expiresAt }

    const shortKey = getShortSlugKey(channel.shortSlug)
    const longKey = getLongSlugKey(channel.longSlug)

    this.channels.set(longKey, storedChannel)
    this.channels.set(shortKey, storedChannel)

    this.setChannelTimeout(shortKey, ttl)
    this.setChannelTimeout(longKey, ttl)

    return true
  }

  async destroyChannel(slug: string): Promise<void> {
    const channel = await this.fetchChannel(slug)
    if (!channel) {
      return
    }

    const shortKey = getShortSlugKey(channel.shortSlug)
    const longKey = getLongSlugKey(channel.longSlug)

    // Clear timeouts
    const shortTimeout = this.timeouts.get(shortKey)
    if (shortTimeout) {
      clearTimeout(shortTimeout)
      this.timeouts.delete(shortKey)
    }

    const longTimeout = this.timeouts.get(longKey)
    if (longTimeout) {
      clearTimeout(longTimeout)
      this.timeouts.delete(longKey)
    }

    this.channels.delete(longKey)
    this.channels.delete(shortKey)
  }
}

export class RedisChannelRepo implements ChannelRepo {
  client: Redis

  constructor() {
    this.client = getRedisClient()
  }

  async createChannel(
    uploaderPeerID: string,
    ttl: number = config.channel.ttl,
  ): Promise<Channel> {
    const shortSlug = await generateShortSlugUntilUnique(
      async (key) => (await this.client.get(key)) !== null,
    )
    const longSlug = await generateLongSlugUntilUnique(
      async (key) => (await this.client.get(key)) !== null,
    )

    const channel: Channel = {
      secret: crypto.randomUUID(),
      longSlug,
      shortSlug,
      uploaderPeerID,
    }
    const channelStr = serializeChannel(channel)

    await this.client.setex(getLongSlugKey(longSlug), ttl, channelStr)
    await this.client.setex(getShortSlugKey(shortSlug), ttl, channelStr)

    return channel
  }

  async fetchChannel(
    slug: string,
    scrubSecret = false,
  ): Promise<Channel | null> {
    const shortChannelStr = await this.client.get(getShortSlugKey(slug))
    if (shortChannelStr) {
      return deserializeChannel(shortChannelStr, scrubSecret)
    }

    const longChannelStr = await this.client.get(getLongSlugKey(slug))
    if (longChannelStr) {
      return deserializeChannel(longChannelStr, scrubSecret)
    }

    return null
  }

  async renewChannel(
    slug: string,
    secret: string,
    ttl: number = config.channel.ttl,
  ): Promise<boolean> {
    const channel = await this.fetchChannel(slug)
    if (!channel || channel.secret !== secret) {
      return false
    }

    await this.client.expire(getLongSlugKey(channel.longSlug), ttl)
    await this.client.expire(getShortSlugKey(channel.shortSlug), ttl)

    return true
  }

  async destroyChannel(slug: string): Promise<void> {
    const channel = await this.fetchChannel(slug)
    if (!channel) {
      return
    }

    await this.client.del(getLongSlugKey(channel.longSlug))
    await this.client.del(getShortSlugKey(channel.shortSlug))
  }
}

let _channelRepo: ChannelRepo | null = null

export function getOrCreateChannelRepo(): ChannelRepo {
  if (!_channelRepo) {
    if (process.env.REDIS_URL) {
      _channelRepo = new RedisChannelRepo()
      console.log('[ChannelRepo] Using Redis storage')
    } else {
      _channelRepo = new MemoryChannelRepo()
      console.log('[ChannelRepo] Using in-memory storage')
    }
  }
  return _channelRepo
}


================================================
FILE: src/components/AddFilesButton.tsx
================================================
import React, { useRef, useCallback, JSX } from 'react'
import { UploadedFile } from '../types'

export default function AddFilesButton({
  onAdd,
}: {
  onAdd: (files: UploadedFile[]) => void
}): JSX.Element {
  const fileInputRef = useRef<HTMLInputElement>(null)

  const handleClick = useCallback(() => {
    fileInputRef.current?.click()
  }, [])

  const handleChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      if (e.target.files) {
        onAdd(Array.from(e.target.files) as UploadedFile[])
        e.target.value = ''
      }
    },
    [onAdd],
  )

  return (
    <>
      <input
        id="add-files-input"
        type="file"
        ref={fileInputRef}
        className="hidden"
        multiple
        onChange={handleChange}
      />
      <button
        id="add-files-button"
        type="button"
        onClick={handleClick}
        className="underline text-stone-700 dark:text-stone-300 hover:text-stone-900 dark:hover:text-stone-100 transition-colors duration-200"
      >
        Add more files
      </button>
    </>
  )
}


================================================
FILE: src/components/CancelButton.tsx
================================================
import React, { JSX } from 'react'

export default function CancelButton({
  onClick,
  text = 'Cancel',
}: {
  onClick: React.MouseEventHandler
  text?: string
}): JSX.Element {
  return (
    <button
      onClick={onClick}
      className="px-4 py-2 text-sm font-medium text-stone-700 dark:text-stone-200 bg-white dark:bg-stone-800 border border-stone-300 dark:border-stone-600 rounded-md hover:bg-stone-50 dark:hover:bg-stone-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-indigo-400"
    >
      {text}
    </button>
  )
}


================================================
FILE: src/components/ConnectionListItem.tsx
================================================
import React, { JSX } from 'react'
import { UploaderConnection, UploaderConnectionStatus } from '../types'
import ProgressBar from './ProgressBar'

export function ConnectionListItem({
  conn,
}: {
  conn: UploaderConnection
}): JSX.Element {
  const getStatusColor = (status: UploaderConnectionStatus) => {
    switch (status) {
      case UploaderConnectionStatus.Uploading:
        return 'bg-blue-500 dark:bg-blue-600'
      case UploaderConnectionStatus.Paused:
        return 'bg-yellow-500 dark:bg-yellow-600'
      case UploaderConnectionStatus.Done:
        return 'bg-green-500 dark:bg-green-600'
      case UploaderConnectionStatus.Closed:
        return 'bg-red-500 dark:bg-red-600'
      case UploaderConnectionStatus.InvalidPassword:
        return 'bg-red-500 dark:bg-red-600'
      default:
        return 'bg-stone-500 dark:bg-stone-600'
    }
  }

  return (
    <div className="w-full mt-4">
      <div className="flex justify-between items-center mb-2">
        <div className="flex items-center space-x-2">
          <span className="text-sm font-medium">
            {conn.browserName && conn.browserVersion ? (
              <>
                {conn.browserName}{' '}
                <span className="text-stone-400">v{conn.browserVersion}</span>
              </>
            ) : (
              'Downloader'
            )}
          </span>
          <span
            className={`px-1.5 py-0.5 text-white rounded-md transition-colors duration-200 font-medium text-[10px] ${getStatusColor(
              conn.status,
            )}`}
          >
            {conn.status.replace(/_/g, ' ')}
          </span>
        </div>

        <div className="text-sm text-stone-500 dark:text-stone-400">
          <div>
            Completed: {conn.completedFiles} / {conn.totalFiles} files
          </div>
          {conn.uploadingFileName &&
            conn.status === UploaderConnectionStatus.Uploading && (
              <div>
                Current file: {Math.round(conn.currentFileProgress * 100)}%
              </div>
            )}
        </div>
      </div>
      <ProgressBar
        value={
          conn.completedFiles === conn.totalFiles
            ? 1
            : (conn.completedFiles + conn.currentFileProgress) / conn.totalFiles
        }
        max={1}
      />
    </div>
  )
}


================================================
FILE: src/components/CopyableInput.tsx
================================================
import React, { JSX } from 'react'
import useClipboard from '../hooks/useClipboard'
import InputLabel from './InputLabel'

export function CopyableInput({
  label,
  value,
}: {
  label: string
  value: string
}): JSX.Element {
  const { hasCopied, onCopy } = useClipboard(value)

  return (
    <div className="flex flex-col w-full">
      <InputLabel>{label}</InputLabel>
      <div className="flex w-full">
        <input
          id={`copyable-input-${label.toLowerCase().replace(/\s+/g, '-')}`}
          className="grow px-3 py-2 text-xs border border-r-0 rounded-l text-stone-900 dark:text-stone-100 bg-white dark:bg-stone-800 border-stone-300 dark:border-stone-600"
          value={value}
          readOnly
        />
        <button
          className="px-4 py-2 text-sm text-stone-700 dark:text-stone-200 bg-stone-100 dark:bg-stone-700 hover:bg-stone-200 dark:hover:bg-stone-600 rounded-r border-t border-r border-b border-stone-300 dark:border-stone-600"
          onClick={onCopy}
        >
          {hasCopied ? 'Copied' : 'Copy'}
        </button>
      </div>
    </div>
  )
}


================================================
FILE: src/components/DownloadButton.tsx
================================================
import React, { JSX } from 'react'

export default function DownloadButton({
  onClick,
}: {
  onClick?: React.MouseEventHandler
}): JSX.Element {
  return (
    <button
      id="download-button"
      onClick={onClick}
      className="h-12 px-4 bg-linear-to-b from-green-500 to-green-600 text-white rounded-md hover:from-green-500 hover:to-green-700 transition-all duration-200 border border-green-600 shadow-sm hover:shadow-md text-shadow"
    >
      Download
    </button>
  )
}


================================================
FILE: src/components/Downloader.tsx
================================================
'use client'

import React, { JSX, useState, useCallback, useEffect } from 'react'
import { useDownloader } from '../hooks/useDownloader'
import PasswordField from './PasswordField'
import UnlockButton from './UnlockButton'
import Loading from './Loading'
import UploadFileList from './UploadFileList'
import DownloadButton from './DownloadButton'
import StopButton from './StopButton'
import ProgressBar from './ProgressBar'
import TitleText from './TitleText'
import ReturnHome from './ReturnHome'
import { pluralize } from '../utils/pluralize'
import { ErrorMessage } from './ErrorMessage'

interface FileInfo {
  fileName: string
  size: number
  type: string
}

export function ConnectingToUploader({
  showTroubleshootingAfter = 3000,
}: {
  showTroubleshootingAfter?: number
}): JSX.Element {
  const [showTroubleshooting, setShowTroubleshooting] = useState(false)

  useEffect(() => {
    const timer = setTimeout(() => {
      setShowTroubleshooting(true)
    }, showTroubleshootingAfter)
    return () => clearTimeout(timer)
  }, [showTroubleshootingAfter])

  if (!showTroubleshooting) {
    return <Loading text="Connecting to uploader..." />
  }

  return (
    <>
      <Loading text="Connecting to uploader..." />

      <div className="bg-stone-50 dark:bg-stone-900 border border-stone-200 dark:border-stone-700 rounded-lg p-8 max-w-md w-full">
        <h2 className="text-xl font-bold mb-4 text-stone-900 dark:text-stone-50">
          Having trouble connecting?
        </h2>

        <div className="space-y-4 text-stone-700 dark:text-stone-300">
          <p>
            FilePizza uses direct peer-to-peer connections, but sometimes the
            connection can get stuck. Here are some possible reasons this can
            happen:
          </p>

          <ul className="list-none space-y-3">
            <li className="flex items-start gap-3 px-4 py-2 rounded-lg bg-stone-100 dark:bg-stone-800">
              <span className="text-base">🚪</span>
              <span className="text-sm">
                The uploader may have closed their browser, lost connectivity,
                or stopped the upload. FilePizza requires the uploader to stay
                online continuously because files are transferred directly
                between browsers.
              </span>
            </li>
            <li className="flex items-start gap-3 px-4 py-2 rounded-lg bg-stone-100 dark:bg-stone-800">
              <span className="text-base">🔒</span>
              <span className="text-sm">
                Your network might have strict firewalls or NAT settings, such
                as having UPnP disabled
              </span>
            </li>
            <li className="flex items-start gap-3 px-4 py-2 rounded-lg bg-stone-100 dark:bg-stone-800">
              <span className="text-base">🌐</span>
              <span className="text-sm">
                Some corporate or school networks block peer-to-peer connections
              </span>
            </li>
          </ul>
        </div>
      </div>
      <ReturnHome />
    </>
  )
}

export function DownloadComplete({
  filesInfo,
  bytesDownloaded,
  totalSize,
}: {
  filesInfo: FileInfo[]
  bytesDownloaded: number
  totalSize: number
}): JSX.Element {
  return (
    <>
      <TitleText>
        You downloaded {pluralize(filesInfo.length, 'file', 'files')}.
      </TitleText>
      <div className="flex flex-col space-y-5 w-full">
        <UploadFileList files={filesInfo} />
        <div className="w-full">
          <ProgressBar value={bytesDownloaded} max={totalSize} />
        </div>
        <ReturnHome />
      </div>
    </>
  )
}

export function DownloadInProgress({
  filesInfo,
  bytesDownloaded,
  totalSize,
  onStop,
}: {
  filesInfo: FileInfo[]
  bytesDownloaded: number
  totalSize: number
  onStop: () => void
}): JSX.Element {
  return (
    <>
      <TitleText>
        You are downloading {pluralize(filesInfo.length, 'file', 'files')}.
      </TitleText>
      <div className="flex flex-col space-y-5 w-full">
        <UploadFileList files={filesInfo} />
        <div className="w-full">
          <ProgressBar value={bytesDownloaded} max={totalSize} />
        </div>
        <div className="flex justify-center w-full">
          <StopButton onClick={onStop} isDownloading />
        </div>
      </div>
    </>
  )
}

export function ReadyToDownload({
  filesInfo,
  onStart,
}: {
  filesInfo: FileInfo[]
  onStart: () => void
}): JSX.Element {
  return (
    <>
      <TitleText>
        You are about to start downloading{' '}
        {pluralize(filesInfo.length, 'file', 'files')}.
      </TitleText>
      <div className="flex flex-col space-y-5 w-full">
        <UploadFileList files={filesInfo} />
        <DownloadButton onClick={onStart} />
      </div>
    </>
  )
}

export function PasswordEntry({
  onSubmit,
  errorMessage,
}: {
  onSubmit: (password: string) => void
  errorMessage: string | null
}): JSX.Element {
  const [password, setPassword] = useState('')
  const handleSubmit = useCallback(
    (e: React.FormEvent) => {
      e.preventDefault()
      onSubmit(password)
    },
    [onSubmit, password],
  )

  return (
    <>
      <TitleText>This download requires a password.</TitleText>
      <div className="flex flex-col space-y-5 w-full">
        <form
          action="#"
          method="post"
          onSubmit={handleSubmit}
          className="w-full"
        >
          <div className="flex flex-col space-y-5 w-full">
            <PasswordField
              value={password}
              onChange={setPassword}
              isRequired
              isInvalid={Boolean(errorMessage)}
            />
            <UnlockButton />
          </div>
        </form>
      </div>
      {errorMessage && <ErrorMessage message={errorMessage} />}
    </>
  )
}

export default function Downloader({
  uploaderPeerID,
}: {
  uploaderPeerID: string
}): JSX.Element {
  const {
    filesInfo,
    isConnected,
    isPasswordRequired,
    isDownloading,
    isDone,
    errorMessage,
    submitPassword,
    startDownload,
    stopDownload,
    totalSize,
    bytesDownloaded,
  } = useDownloader(uploaderPeerID)

  if (isDone && filesInfo) {
    return (
      <DownloadComplete
        filesInfo={filesInfo}
        bytesDownloaded={bytesDownloaded}
        totalSize={totalSize}
      />
    )
  }

  if (isPasswordRequired) {
    return (
      <PasswordEntry errorMessage={errorMessage} onSubmit={submitPassword} />
    )
  }

  if (errorMessage) {
    return (
      <>
        <ErrorMessage message={errorMessage} />
        <ReturnHome />
      </>
    )
  }

  if (isDownloading && filesInfo) {
    return (
      <DownloadInProgress
        filesInfo={filesInfo}
        bytesDownloaded={bytesDownloaded}
        totalSize={totalSize}
        onStop={stopDownload}
      />
    )
  }

  if (filesInfo) {
    return <ReadyToDownload filesInfo={filesInfo} onStart={startDownload} />
  }

  if (!isConnected) {
    return <ConnectingToUploader />
  }

  return <Loading text="Uh oh... Something went wrong." />
}


================================================
FILE: src/components/DropZone.tsx
================================================
import React, { JSX, useState, useCallback, useEffect, useRef } from 'react'
import { extractFileList } from '../fs'

export default function DropZone({
  onDrop,
}: {
  onDrop: (files: File[]) => void
}): JSX.Element {
  const [isDragging, setIsDragging] = useState(false)
  const [fileCount, setFileCount] = useState(0)
  const fileInputRef = useRef<HTMLInputElement>(null)

  const handleDragEnter = useCallback((e: DragEvent) => {
    e.preventDefault()
    setIsDragging(true)
    setFileCount(e.dataTransfer?.items.length || 0)
  }, [])

  const handleDragLeave = useCallback((e: DragEvent) => {
    e.preventDefault()

    const currentTarget =
      e.currentTarget === window ? window.document : e.currentTarget
    if (
      e.relatedTarget &&
      currentTarget instanceof Node &&
      currentTarget.contains(e.relatedTarget as Node)
    ) {
      return
    }

    setIsDragging(false)
  }, [])

  const handleDragOver = useCallback((e: DragEvent) => {
    e.preventDefault()
    if (e.dataTransfer) {
      e.dataTransfer.dropEffect = 'copy'
    }
  }, [])

  const handleDrop = useCallback(
    async (e: DragEvent) => {
      e.preventDefault()
      setIsDragging(false)

      if (e.dataTransfer) {
        const files = await extractFileList(e)
        onDrop(files)
      }
    },
    [onDrop],
  )

  const handleClick = useCallback(() => {
    fileInputRef.current?.click()
  }, [])

  const handleFileInputChange = useCallback(
    async (e: React.ChangeEvent<HTMLInputElement>) => {
      if (e.target.files) {
        const files = Array.from(e.target.files)
        onDrop(files)
      }
    },
    [onDrop],
  )

  useEffect(() => {
    window.addEventListener('dragenter', handleDragEnter)
    window.addEventListener('dragleave', handleDragLeave)
    window.addEventListener('dragover', handleDragOver)
    window.addEventListener('drop', handleDrop)

    return () => {
      window.removeEventListener('dragenter', handleDragEnter)
      window.removeEventListener('dragleave', handleDragLeave)
      window.removeEventListener('dragover', handleDragOver)
      window.removeEventListener('drop', handleDrop)
    }
  }, [handleDragEnter, handleDragLeave, handleDragOver, handleDrop])

  return (
    <>
      <div
        className={`fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center text-2xl text-white transition-opacity duration-300 backdrop-blur-sm z-50 ${
          isDragging ? 'opacity-100 visible' : 'opacity-0 invisible'
        }`}
      >
        Drop to select {fileCount} file{fileCount !== 1 ? 's' : ''}
      </div>
      <input
        type="file"
        ref={fileInputRef}
        className="hidden"
        onChange={handleFileInputChange}
        multiple
      />
      <button
        id="drop-zone-button"
        className="block cursor-pointer relative py-3 px-6 text-base font-bold text-stone-700 dark:text-stone-200 bg-white dark:bg-stone-800 border-2 border-stone-700 dark:border-stone-700 rounded-lg transition-all duration-300 ease-in-out outline-none hover:shadow-md active:shadow-inner focus:shadow-outline"
        onClick={handleClick}
      >
        <span className="text-center text-stone-700 dark:text-stone-200">
          Drop a file to get started
        </span>
      </button>
    </>
  )
}


================================================
FILE: src/components/ErrorMessage.tsx
================================================
import { JSX } from 'react'

export function ErrorMessage({ message }: { message: string }): JSX.Element {
  return (
    <div
      className="bg-red-100 dark:bg-red-900/30 border border-red-400 dark:border-red-700 text-red-700 dark:text-red-300 px-4 py-3 rounded relative"
      role="alert"
    >
      <span className="block sm:inline">{message}</span>
    </div>
  )
}


================================================
FILE: src/components/Footer.tsx
================================================
'use client'

import React, { JSX, useCallback } from 'react'

const DONATE_HREF =
  'https://commerce.coinbase.com/checkout/247b6ffe-fb4e-47a8-9a76-e6b7ef83ea22'

function FooterLink({
  href,
  children,
}: {
  href: string
  children: React.ReactNode
}): JSX.Element {
  return (
    <a
      className="text-stone-600 dark:text-stone-400 underline hover:text-stone-800 dark:hover:text-stone-200"
      href={href}
      target="_blank"
      rel="noopener noreferrer"
    >
      {children}
    </a>
  )
}

export function Footer(): JSX.Element {
  const handleDonate = useCallback(() => {
    window.location.href = DONATE_HREF
  }, [])

  return (
    <>
      <div className="h-[100px]" /> {/* Spacer to account for footer height */}
      <footer className="fixed bottom-0 left-0 right-0 text-center py-2.5 pb-4 text-xs border-t border-stone-200 dark:border-stone-700 shadow-[0_-1px_2px_rgba(0,0,0,0.04)] dark:shadow-[0_-1px_2px_rgba(255,255,255,0.04)] bg-white dark:bg-stone-900">
        <div className="flex flex-col items-center space-y-1 px-4 sm:px-6 md:px-8">
          <div className="flex items-center space-x-2">
            <p className="text-stone-600 dark:text-stone-400">
              <strong>Like FilePizza v2?</strong> Support its development!{' '}
            </p>
            <button
              className="px-1.5 py-0.5 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors duration-200 font-medium text-[10px]"
              onClick={handleDonate}
            >
              Donate
            </button>
          </div>

          <p className="text-stone-600 dark:text-stone-400">
            Cooked up by{' '}
            <FooterLink href="http://kern.io">Alex Kern</FooterLink> &amp;{' '}
            <FooterLink href="https://github.com/neerajbaid">
              Neeraj Baid
            </FooterLink>{' '}
            while eating <strong>Sliver</strong> @ UC Berkeley &middot;{' '}
            <FooterLink href="https://github.com/kern/filepizza#faq">
              FAQ
            </FooterLink>{' '}
            &middot;{' '}
            <FooterLink href="https://github.com/kern/filepizza">
              Fork us
            </FooterLink>
          </p>
        </div>
      </footer>
    </>
  )
}

export default Footer


================================================
FILE: src/components/InputLabel.tsx
================================================
import React, { JSX } from 'react'

export default function InputLabel({
  children,
  hasError = false,
  tooltip,
}: {
  children: React.ReactNode
  hasError?: boolean
  tooltip?: string
}): JSX.Element {
  return (
    <div className="relative flex items-center gap-1">
      <label
        className={`text-[10px] mb-0.5 font-bold group relative inline-block ${
          hasError ? 'text-red-500' : 'text-stone-400'
        }`}
      >
        {children}
      </label>
      {tooltip && (
        <div className="relative">
          <div
            className="text-[11px] text-stone-400 dark:text-stone-400 cursor-help hover:opacity-80 peer focus:opacity-80"
            role="button"
            aria-label="Show tooltip"
            tabIndex={0}
          >
            ⓘ
          </div>
          <div className="pointer-events-none absolute left-full top-1/2 -translate-y-1/2 ml-1 opacity-0 peer-hover:opacity-100 peer-focus:opacity-100 transition-opacity duration-200 z-10">
            <div className="bg-stone-100 dark:bg-stone-800 text-stone-800 dark:text-stone-100 text-xs rounded px-3 py-2 w-[320px] border border-stone-200 dark:border-stone-700 shadow-lg">
              {tooltip}
            </div>
          </div>
        </div>
      )}
    </div>
  )
}


================================================
FILE: src/components/Loading.tsx
================================================
import React, { JSX } from 'react'

export default function Loading({ text }: { text: string }): JSX.Element {
  return (
    <div className="flex flex-col items-center">
      <p className="text-sm text-stone-600 dark:text-stone-400 mt-2">{text}</p>
    </div>
  )
}


================================================
FILE: src/components/ModeToggle.tsx
================================================
'use client'

import { useTheme } from 'next-themes'
import { JSX } from 'react'
function LightModeIcon(): JSX.Element {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      strokeWidth={1.5}
      stroke="currentColor"
      className="w-4 h-4 block dark:hidden"
    >
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
      />
    </svg>
  )
}

function DarkModeIcon(): JSX.Element {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      strokeWidth={1.5}
      stroke="currentColor"
      className="w-4 h-4 hidden dark:block"
    >
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
      />
    </svg>
  )
}

export function ModeToggle(): JSX.Element {
  const { setTheme, resolvedTheme } = useTheme()

  return (
    <button
      onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
      className="fixed top-4 right-4 border rounded-md w-6 h-6 flex items-center justify-center"
    >
      <span className="sr-only">Toggle mode</span>
      <LightModeIcon />
      <DarkModeIcon />
    </button>
  )
}


================================================
FILE: src/components/PasswordField.tsx
================================================
import React, { JSX, useCallback } from 'react'
import InputLabel from './InputLabel'

export default function PasswordField({
  value,
  onChange,
  isRequired = false,
  isInvalid = false,
}: {
  value: string
  onChange: (v: string) => void
  isRequired?: boolean
  isInvalid?: boolean
}): JSX.Element {
  const handleChange = useCallback(
    function (e: React.ChangeEvent<HTMLInputElement>): void {
      onChange(e.target.value)
    },
    [onChange],
  )

  return (
    <div className="flex flex-col w-full">
      <InputLabel
        hasError={isInvalid}
        tooltip="The downloader must provide this password to start downloading the file. If you don't specify a password here, any downloader with the link to the file will be able to download it. It is not used to encrypt the file, as this is performed by WebRTC's DTLS already."
      >
        {isRequired ? 'Password' : 'Password (optional)'}
      </InputLabel>
      <input
        autoFocus
        type="password"
        className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 ${
          isInvalid
            ? 'border-red-500 dark:border-red-400'
            : 'border-stone-300 dark:border-stone-600'
        } bg-white dark:bg-stone-800 text-stone-900 dark:text-stone-100`}
        placeholder="Enter a secret password for this slice of FilePizza..."
        value={value}
        onChange={handleChange}
      />
    </div>
  )
}


================================================
FILE: src/components/ProgressBar.tsx
================================================
import React, { JSX } from 'react'

export default function ProgressBar({
  value,
  max,
}: {
  value: number
  max: number
}): JSX.Element {
  const percentage = (value / max) * 100
  const isComplete = value === max

  return (
    <div
      id="progress-bar"
      className="w-full h-12 bg-stone-200 dark:bg-stone-700 rounded-md overflow-hidden relative shadow-sm"
    >
      <div className="absolute inset-0 flex items-center justify-center">
        <span className="text-black font-bold">{Math.round(percentage)}%</span>
      </div>
      <div
        id="progress-bar-fill"
        className={`h-full ${
          isComplete
            ? 'bg-linear-to-b from-green-500 to-green-600'
            : 'bg-linear-to-b from-blue-500 to-blue-600'
        } transition-all duration-300 ease-in-out`}
        style={{ width: `${percentage}%` }}
      />
      <div className="absolute inset-0 flex items-center justify-center">
        <span
          id="progress-percentage"
          className="text-white font-bold text-shadow"
        >
          {Math.round(percentage)}%
        </span>
      </div>
    </div>
  )
}


================================================
FILE: src/components/QueryClientProvider.tsx
================================================
'use client'

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient()

export default function FilePizzaQueryClientProvider({
  children,
}: {
  children: React.ReactNode
}): React.ReactElement {
  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}


================================================
FILE: src/components/ReportTermsViolationButton.tsx
================================================
'use client'

import { JSX } from 'react'
import { useWebRTCPeer } from './WebRTCProvider'
import { useCallback, useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import CancelButton from './CancelButton'

export default function ReportTermsViolationButton({
  uploaderPeerID,
  slug,
}: {
  uploaderPeerID: string
  slug: string
}): JSX.Element {
  const { peer } = useWebRTCPeer()
  const [showModal, setShowModal] = useState(false)
  const [isReporting, setIsReporting] = useState(false)

  const reportMutation = useMutation({
    mutationFn: async () => {
      const response = await fetch(`/api/destroy`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ slug }),
      })
      if (!response.ok) {
        throw new Error('Failed to report violation')
      }
      return response.json()
    },
  })

  const handleReport = useCallback(() => {
    try {
      // Destroy the channel so no further downloads can be made.
      setIsReporting(true)
      reportMutation.mutate()

      // Send a report message to the uploader to hard-redirect them to the reported page.
      // The uploader will broadcast a report message to all connections, which will hard-redirect all downloaders to the reported page.
      const conn = peer.connect(uploaderPeerID, {
        metadata: { type: 'report' },
      })

      // Set a timeout to redirect after 2 seconds even if connection doesn't open
      const timeout = setTimeout(() => {
        conn.close()
        window.location.href = '/reported'
      }, 2000)

      conn.on('open', () => {
        clearTimeout(timeout)
        conn.close()
        window.location.href = '/reported'
      })
    } catch (error) {
      console.error('Failed to report violation', error)
      setIsReporting(false)
    }
  }, [peer, uploaderPeerID])

  return (
    <>
      <div className="flex justify-center">
        <button
          onClick={() => setShowModal(true)}
          className="text-sm text-red-600 dark:text-red-400 hover:underline transition-colors duration-200"
          aria-label="Report terms violation"
        >
          Report suspicious pizza delivery
        </button>
      </div>

      {showModal && (
        <div
          className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50"
          role="dialog"
          aria-modal="true"
          aria-labelledby="modal-title"
          onClick={() => setShowModal(false)}
        >
          <div
            className="bg-stone-50 dark:bg-stone-900 border border-stone-200 dark:border-stone-700 rounded-lg p-8 max-w-md w-full shadow-lg"
            onClick={(e) => e.stopPropagation()}
          >
            <h2
              id="modal-title"
              className="text-xl font-bold mb-4 text-stone-900 dark:text-stone-50"
            >
              Found a suspicious delivery?
            </h2>

            <div className="space-y-4 text-stone-700 dark:text-stone-300">
              <p>
                Before reporting this delivery, please note our FilePizza terms:
              </p>

              <ul className="list-none space-y-3">
                <li className="flex items-start gap-3 px-4 py-2 rounded-lg bg-stone-100 dark:bg-stone-800">
                  <span className="text-base">✅</span>
                  <span className="text-sm">
                    Only upload files you have the right to share
                  </span>
                </li>
                <li className="flex items-start gap-3 px-4 py-2 rounded-lg bg-stone-100 dark:bg-stone-800">
                  <span className="text-base">🔒</span>
                  <span className="text-sm">
                    Share download links only with known recipients
                  </span>
                </li>
                <li className="flex items-start gap-3 px-4 py-2 rounded-lg bg-stone-100 dark:bg-stone-800">
                  <span className="text-base">⚠️</span>
                  <span className="text-sm">
                    No illegal or harmful content allowed
                  </span>
                </li>
              </ul>

              <p>
                If you've spotted a violation of these terms, click Report to
                halt its delivery.
              </p>
            </div>

            <div className="mt-6 flex justify-end space-x-4">
              <CancelButton onClick={() => setShowModal(false)} />
              <button
                disabled={isReporting}
                onClick={handleReport}
                className={`px-4 py-2 bg-linear-to-b from-red-500 to-red-600 text-white rounded-md border border-red-600 shadow-sm text-shadow disabled:opacity-50 disabled:cursor-not-allowed enabled:hover:from-red-500 enabled:hover:to-red-700 enabled:hover:shadow-md transition-all duration-200`}
                aria-label="Confirm report"
              >
                {isReporting ? 'Reporting...' : 'Report'}
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  )
}


================================================
FILE: src/components/ReturnHome.tsx
================================================
import { Link } from 'next-view-transitions'
import { JSX } from 'react'

export default function ReturnHome(): JSX.Element {
  return (
    <div className="flex justify-center">
      <Link
        href="/"
        className="text-stone-500 dark:text-stone-200 hover:underline"
      >
        Serve up a fresh slice &raquo;
      </Link>
    </div>
  )
}


================================================
FILE: src/components/Spinner.tsx
================================================
'use client'

import React, { JSX } from 'react'
import { useRotatingSpinner } from '../hooks/useRotatingSpinner'

function Pizza({ isRotating }: { isRotating?: boolean }): JSX.Element {
  return (
    <svg
      width="300"
      height="300"
      viewBox="0 0 577 576"
      fill="none"
      className={isRotating ? 'animate-spin-slow' : ''}
      xmlns="http://www.w3.org/2000/svg"
      role="img"
      aria-label={isRotating ? 'Rotating pizza' : 'Pizza'}
    >
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M282.03 3C305.481 3 342.363 3.834 364.169 9.196C379.23 12.899 411.295 34.038 425.12 40.07C444.633 48.582 473.307 61.063 488.89 72.683C504.759 84.517 514.985 106.605 525.825 121.285C538.485 138.43 554.853 158.059 562.267 177.47C568.947 194.96 568.68 217.508 569.719 237.83C570.547 254.018 573.27 267.218 573.27 286.732C573.27 305.214 574.801 323.938 571.428 341.441C568.674 355.73 548.336 389.584 543.458 403.014C534.262 428.329 525.566 457.372 511.148 477.129C498.107 494.998 465.852 512.222 448.562 524.724C432.604 536.263 419.877 553.349 403.411 560.308C392.817 564.786 360.764 565.314 348.757 566.92C324.981 570.1 300.039 572.855 269.796 572.855C252.164 572.855 200.212 555.435 183.89 552.32C154.282 546.671 131.181 537.396 109.506 524.109C92.2349 513.522 73.1809 481.362 60.3559 466.431C45.9569 449.669 26.4779 423.234 18.0389 404.732C2.3219 370.274 7.3699 356.493 3.6579 297.645C2.4529 278.549 2.8439 255.235 5.3239 233.582C6.8889 219.925 9.2849 206.928 12.6369 196.067C22.1159 165.353 29.6899 141.087 49.6239 116.134C65.5429 96.206 95.6839 73.766 117.973 59.173C136.328 47.154 151.148 28.521 170.178 19.875C205.749 3.714 240.075 3 282.03 3Z"
        fill="#A9652E"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M282.229 10.8268C304.937 10.8268 340.651 11.6348 361.766 16.8268C376.351 20.4128 407.401 40.8818 420.788 46.7228C439.682 54.9658 467.449 67.0518 482.539 78.3038C497.905 89.7618 507.807 111.151 518.304 125.366C530.563 141.968 546.413 160.976 553.592 179.772C560.06 196.709 559.802 218.542 560.808 238.221C561.609 253.896 564.246 266.678 564.246 285.575C564.246 303.471 565.729 321.602 562.462 338.551C559.796 352.388 540.102 385.169 535.378 398.174C526.474 422.688 518.053 450.811 504.091 469.943C491.464 487.246 460.229 503.924 443.487 516.031C428.035 527.205 415.711 543.749 399.767 550.488C389.508 554.824 358.47 555.335 346.843 556.89C323.82 559.97 299.668 562.638 270.382 562.638C253.308 562.638 203.001 545.769 187.196 542.753C158.526 537.283 136.157 528.301 115.167 515.435C98.4439 505.184 79.9929 474.042 67.5739 459.584C53.6319 443.352 34.7689 417.754 26.5969 399.838C11.3779 366.471 16.2659 353.126 12.6709 296.142C11.5049 277.65 11.8829 255.075 14.2849 234.108C15.7999 220.883 18.1209 208.298 21.3659 197.78C30.5449 168.039 37.8789 144.541 57.1819 120.378C72.5969 101.082 101.784 79.3518 123.366 65.2208C141.141 53.5828 155.491 35.5398 173.919 27.1668C208.363 11.5178 241.603 10.8268 282.229 10.8268Z"
        fill="#C3783A"
      />
      <path
        d="M282.03 3C305.481 3 342.363 3.834 364.169 9.196C379.23 12.899 411.295 34.038 425.12 40.07C444.633 48.582 473.307 61.063 488.89 72.683C504.759 84.517 514.985 106.605 525.825 121.285C538.485 138.43 554.853 158.059 562.267 177.47C568.947 194.96 568.68 217.508 569.719 237.83C570.547 254.018 573.27 267.218 573.27 286.732C573.27 305.214 574.801 323.938 571.428 341.441C568.674 355.73 548.336 389.584 543.458 403.014C534.262 428.329 525.566 457.372 511.148 477.129C498.107 494.998 465.852 512.222 448.562 524.724C432.604 536.263 419.877 553.349 403.411 560.308C392.817 564.786 360.764 565.314 348.757 566.92C324.981 570.1 300.039 572.855 269.796 572.855C252.164 572.855 200.212 555.435 183.89 552.32C154.282 546.671 131.181 537.396 109.506 524.109C92.2349 513.522 73.1809 481.362 60.3559 466.431C45.9569 449.669 26.4779 423.234 18.0389 404.732C2.3219 370.274 7.3699 356.493 3.6579 297.645C2.4529 278.549 2.8439 255.235 5.3239 233.582C6.8889 219.925 9.2849 206.928 12.6369 196.067C22.1159 165.353 29.6899 141.087 49.6239 116.134C65.5429 96.206 95.6839 73.766 117.973 59.173C136.328 47.154 151.148 28.521 170.178 19.875C205.749 3.714 240.075 3 282.03 3Z"
        stroke="#521E11"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M288.827 27.9578C322.962 27.9578 343.427 30.3268 369.094 42.1828C383.616 48.8908 404.212 62.3178 418.983 70.9598C434.772 80.1978 463.535 85.2468 476.196 97.4908C488.29 109.188 493.836 123.556 503.41 137.451C517.367 157.706 535.196 169.274 541.456 196.128C543.311 204.085 541.92 225.168 542.898 239.887C544.246 260.168 547.602 274.995 547.602 286.732C547.602 301.57 547.444 311.961 543.812 325.591C538.352 346.078 527.707 369.271 519.219 389.062C508.6 413.822 500.922 440.868 483.459 460.315C469.9 475.416 452.058 483.657 435.682 495.821C418.507 508.578 403.409 526.345 383.46 534.196C370.534 539.284 357.051 537.558 343.209 539.098C325.657 541.052 307.496 545.507 288.827 545.507C260.205 545.507 228.873 532.544 200.966 523.887C177.285 516.54 156.299 514.448 139.127 503.351C113.983 487.102 106.472 470.413 88.5909 445.642C73.8849 425.27 54.3409 407.02 47.0689 380.547C40.8269 357.829 38.6499 316.505 38.6499 291.405C38.6499 262.136 34.9219 229.322 43.8959 203.076C55.1649 170.115 63.2489 143.39 86.8969 119.311C100.909 105.044 127.85 90.9798 143.827 79.0738C161.812 65.6698 174.247 48.0068 196.099 40.2028C212.441 34.3678 227.463 33.8128 246.799 32.1528C259.618 31.0518 276.67 27.9578 288.827 27.9578Z"
        fill="#B52720"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M288.946 35.8318C322.042 35.8318 341.885 38.1288 366.77 49.6238C380.851 56.1278 400.82 69.1458 415.141 77.5248C430.451 86.4818 458.338 91.3778 470.614 103.25C482.34 114.59 487.717 128.521 497 141.994C510.532 161.633 527.819 172.848 533.888 198.885C535.687 206.6 534.338 227.041 535.286 241.313C536.593 260.976 539.847 275.352 539.847 286.732C539.847 301.118 539.694 311.193 536.172 324.408C530.879 344.272 520.557 366.76 512.327 385.948C502.031 409.955 494.587 436.178 477.656 455.033C464.509 469.675 447.211 477.665 431.332 489.459C414.68 501.828 400.041 519.054 380.7 526.666C368.167 531.599 355.094 529.925 341.673 531.419C324.655 533.313 307.047 537.633 288.946 537.633C261.195 537.633 230.817 525.065 203.759 516.67C180.798 509.547 160.451 507.519 143.802 496.76C119.423 481.005 112.14 464.824 94.8029 440.806C80.5449 421.055 61.5949 403.36 54.5439 377.693C48.4929 355.665 46.3809 315.599 46.3809 291.263C46.3809 262.884 42.7679 231.069 51.4679 205.621C62.3939 173.663 70.2319 147.752 93.1609 124.406C106.746 110.572 132.867 96.9368 148.358 85.3918C165.796 72.3968 177.853 55.2708 199.04 47.7048C214.885 42.0468 229.45 41.5088 248.197 39.8998C260.626 38.8318 277.159 35.8318 288.946 35.8318Z"
        fill="#E03C32"
      />
      <path
        d="M288.827 27.9578C322.962 27.9578 343.427 30.3268 369.094 42.1828C383.616 48.8908 404.212 62.3178 418.983 70.9598C434.772 80.1978 463.535 85.2468 476.196 97.4908C488.29 109.188 493.836 123.556 503.41 137.451C517.367 157.706 535.196 169.274 541.456 196.128C543.311 204.085 541.92 225.168 542.898 239.887C544.246 260.168 547.602 274.995 547.602 286.732C547.602 301.57 547.444 311.961 543.812 325.591C538.352 346.078 527.707 369.271 519.219 389.062C508.6 413.822 500.922 440.868 483.459 460.315C469.9 475.416 452.058 483.657 435.682 495.821C418.507 508.578 403.409 526.345 383.46 534.196C370.534 539.284 357.051 537.558 343.209 539.098C325.657 541.052 307.496 545.507 288.827 545.507C260.205 545.507 228.873 532.544 200.966 523.887C177.285 516.54 156.299 514.448 139.127 503.351C113.983 487.102 106.472 470.413 88.5909 445.642C73.8849 425.27 54.3409 407.02 47.0689 380.547C40.8269 357.829 38.6499 316.505 38.6499 291.405C38.6499 262.136 34.9219 229.322 43.8959 203.076C55.1649 170.115 63.2489 143.39 86.8969 119.311C100.909 105.044 127.85 90.9798 143.827 79.0738C161.812 65.6698 174.247 48.0068 196.099 40.2028C212.441 34.3678 227.463 33.8128 246.799 32.1528C259.618 31.0518 276.67 27.9578 288.827 27.9578Z"
        stroke="#521E11"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M288.827 47.5979C328.62 47.5979 358.795 55.9589 392.033 75.4339C438.72 102.789 424.814 100.89 453.232 127.777C476.652 149.935 494.302 155.385 503.41 173.492C516.145 198.809 522.449 258.95 525.635 286.732C527.837 305.938 510.317 338.851 503.41 353.367C485.868 390.233 489.753 419.213 473.846 439.761C453.911 465.512 415.108 479.154 403.411 491.436C372.26 524.145 362.7 520.159 300.403 515.358C281.069 513.868 244.487 518.845 228.98 511.2C191.2 492.571 186.156 484.616 171.894 476.258C150.279 463.591 124.1 451.699 107.704 434.742C80.6339 406.746 57.4639 365.527 60.6499 319.407C63.3479 280.351 69.4449 213.429 76.8329 187.094C81.9619 168.814 97.0939 140.021 116.199 123.684C124.097 116.931 155.922 95.0779 164.746 89.3089C174.983 82.6169 210.847 62.8899 222 58.2989C242.886 49.6999 265.646 47.5979 288.827 47.5979Z"
        fill="#EBA900"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M288.998 57.0408C327.195 57.0408 356.16 65.0668 388.065 83.7598C432.881 110.018 419.532 108.196 446.811 134.004C469.291 155.274 486.234 160.505 494.977 177.886C507.201 202.188 513.252 259.918 516.31 286.586C518.424 305.021 501.607 336.615 494.977 350.548C478.138 385.936 481.867 413.755 466.598 433.478C447.462 458.197 410.215 471.292 398.988 483.081C369.085 514.479 359.909 510.652 300.11 506.044C281.551 504.613 246.436 509.392 231.551 502.052C195.285 484.171 190.444 476.534 176.754 468.512C156.006 456.353 130.876 444.938 115.137 428.66C89.1529 401.787 66.9119 362.221 69.9699 317.95C72.5609 280.46 78.4129 216.222 85.5049 190.943C90.4279 173.396 104.953 145.758 123.292 130.076C130.873 123.593 161.422 102.617 169.893 97.0788C179.719 90.6558 214.145 71.7198 224.851 67.3118C244.899 59.0578 266.746 57.0408 288.998 57.0408Z"
        fill="#FCC534"
      />
      <path
        d="M288.827 47.5979C328.62 47.5979 358.795 55.9589 392.033 75.4339C438.72 102.789 424.814 100.89 453.232 127.777C476.652 149.935 494.302 155.385 503.41 173.492C516.145 198.809 522.449 258.95 525.635 286.732C527.837 305.938 510.317 338.851 503.41 353.367C485.868 390.233 489.753 419.213 473.846 439.761C453.911 465.512 415.108 479.154 403.411 491.436C372.26 524.145 362.7 520.159 300.403 515.358C281.069 513.868 244.487 518.845 228.98 511.2C191.2 492.571 186.156 484.616 171.894 476.258C150.279 463.591 124.1 451.699 107.704 434.742C80.6339 406.746 57.4639 365.527 60.6499 319.407C63.3479 280.351 69.4449 213.429 76.8329 187.094C81.9619 168.814 97.0939 140.021 116.199 123.684C124.097 116.931 155.922 95.0779 164.746 89.3089C174.983 82.6169 210.847 62.8899 222 58.2989C242.886 49.6999 265.646 47.5979 288.827 47.5979Z"
        stroke="#521E11"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M151.149 221.694C132.103 221.694 116.663 206.254 116.663 187.208C116.663 168.162 132.103 152.722 151.149 152.722C170.195 152.722 185.635 168.162 185.635 187.208C185.635 206.254 170.195 221.694 151.149 221.694Z"
        fill="#D53B1C"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M129.892 214.356C125.58 208.475 123.026 201.182 123.026 193.286C123.026 173.812 138.56 158.002 157.695 158.002C164.428 158.002 170.716 159.96 176.037 163.346C181.981 169.543 185.635 177.952 185.635 187.208C185.635 206.241 170.182 221.694 151.149 221.694C143.132 221.694 135.751 218.952 129.892 214.356Z"
        fill="#E84827"
      />
      <path
        d="M151.149 221.694C132.103 221.694 116.663 206.254 116.663 187.208C116.663 168.162 132.103 152.722 151.149 152.722C170.195 152.722 185.635 168.162 185.635 187.208C185.635 206.254 170.195 221.694 151.149 221.694Z"
        stroke="#8D3A2B"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M147.274 173.163C144.587 173.163 142.409 170.985 142.409 168.299C142.409 165.612 144.587 163.435 147.274 163.435C149.961 163.435 152.139 165.612 152.139 168.299C152.139 170.985 149.961 173.163 147.274 173.163Z"
        fill="#F5A27F"
      />
      <path
        d="M162.685 209.193C158.559 209.193 155.214 205.848 155.214 201.722C155.214 197.596 158.559 194.251 162.685 194.251C166.811 194.251 170.156 197.596 170.156 201.722C170.156 205.848 166.811 209.193 162.685 209.193Z"
        fill="#F5A27F"
      />
      <path
        d="M167.549 180.431C163.535 180.431 160.282 177.177 160.282 173.164C160.282 169.15 163.535 165.897 167.549 165.897C171.562 165.897 174.816 169.15 174.816 173.164C174.816 177.177 171.562 180.431 167.549 180.431Z"
        fill="#F5A27F"
      />
      <path
        d="M130.322 183.492C128.31 183.492 126.679 181.861 126.679 179.849C126.679 177.837 128.31 176.206 130.322 176.206C132.334 176.206 133.965 177.837 133.965 179.849C133.965 181.861 132.334 183.492 130.322 183.492Z"
        fill="#F5A27F"
      />
      <path
        d="M148.934 213.169C147.597 213.169 146.513 212.085 146.513 210.748C146.513 209.411 147.597 208.327 148.934 208.327C150.271 208.327 151.355 209.411 151.355 210.748C151.355 212.085 150.271 213.169 148.934 213.169Z"
        fill="#F5A27F"
      />
      <path
        d="M153.775 179.541C152.438 179.541 151.355 178.457 151.355 177.121C151.355 175.784 152.438 174.701 153.775 174.701C155.111 174.701 156.195 175.784 156.195 177.121C156.195 178.457 155.111 179.541 153.775 179.541Z"
        fill="#F5A27F"
      />
      <path
        d="M139.864 208.328C134.854 208.328 130.793 204.267 130.793 199.257C130.793 194.247 134.854 190.186 139.864 190.186C144.874 190.186 148.935 194.247 148.935 199.257C148.935 204.267 144.874 208.328 139.864 208.328Z"
        fill="#F5A27F"
      />
      <path
        d="M156.917 189.444C155.682 189.444 154.681 188.443 154.681 187.208C154.681 185.973 155.682 184.972 156.917 184.972C158.152 184.972 159.153 185.973 159.153 187.208C159.153 188.443 158.152 189.444 156.917 189.444Z"
        fill="#B73D2D"
      />
      <path
        d="M175.918 184.972C174.683 184.972 173.682 183.971 173.682 182.736C173.682 181.501 174.683 180.5 175.918 180.5C177.153 180.5 178.154 181.501 178.154 182.736C178.154 183.971 177.153 184.972 175.918 184.972Z"
        fill="#B73D2D"
      />
      <path
        d="M141.661 180.501C140.426 180.501 139.425 179.5 139.425 178.265C139.425 177.03 140.426 176.029 141.661 176.029C142.896 176.029 143.897 177.03 143.897 178.265C143.897 179.5 142.896 180.501 141.661 180.501Z"
        fill="#B73D2D"
      />
      <path
        d="M127.838 191.679C126.603 191.679 125.602 190.678 125.602 189.443C125.602 188.208 126.603 187.207 127.838 187.207C129.073 187.207 130.074 188.208 130.074 189.443C130.074 190.678 129.073 191.679 127.838 191.679Z"
        fill="#B73D2D"
      />
      <path
        d="M173.682 196.151C172.447 196.151 171.446 195.15 171.446 193.915C171.446 192.68 172.447 191.679 173.682 191.679C174.917 191.679 175.918 192.68 175.918 193.915C175.918 195.15 174.917 196.151 173.682 196.151Z"
        fill="#B73D2D"
      />
      <path
        d="M367.24 302.877C350.879 302.877 337.616 289.614 337.616 273.253C337.616 256.892 350.879 243.629 367.24 243.629C383.601 243.629 396.864 256.892 396.864 273.253C396.864 289.614 383.601 302.877 367.24 302.877Z"
        fill="#D53B1C"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M348.982 296.575C345.277 291.523 343.083 285.257 343.083 278.475C343.083 261.747 356.427 248.166 372.863 248.166C378.648 248.166 384.05 249.848 388.621 252.758C393.726 258.081 396.864 265.304 396.864 273.253C396.864 289.603 383.59 302.877 367.241 302.877C360.355 302.877 354.015 300.522 348.982 296.575Z"
        fill="#E84827"
      />
      <path
        d="M367.24 302.877C350.879 302.877 337.616 289.614 337.616 273.253C337.616 256.892 350.879 243.629 367.24 243.629C383.601 243.629 396.864 256.892 396.864 273.253C396.864 289.614 383.601 302.877 367.24 302.877Z"
        stroke="#8D3A2B"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M363.912 261.189C361.604 261.189 359.733 259.318 359.733 257.01C359.733 254.702 361.604 252.831 363.912 252.831C366.22 252.831 368.091 254.702 368.091 257.01C368.091 259.318 366.22 261.189 363.912 261.189Z"
        fill="#F5A27F"
      />
      <path
        d="M377.149 292.137C373.604 292.137 370.731 289.264 370.731 285.72C370.731 282.176 373.604 279.303 377.149 279.303C380.693 279.303 383.567 282.176 383.567 285.72C383.567 289.264 380.693 292.137 377.149 292.137Z"
        fill="#F5A27F"
      />
      <path
        d="M381.328 267.432C377.88 267.432 375.085 264.637 375.085 261.189C375.085 257.741 377.88 254.946 381.328 254.946C384.776 254.946 387.571 257.741 387.571 261.189C387.571 264.637 384.776 267.432 381.328 267.432Z"
        fill="#F5A27F"
      />
      <path
        d="M349.349 270.061C347.621 270.061 346.22 268.66 346.22 266.932C346.22 265.204 347.621 263.803 349.349 263.803C351.077 263.803 352.478 265.204 352.478 266.932C352.478 268.66 351.077 270.061 349.349 270.061Z"
        fill="#F5A27F"
      />
      <path
        d="M365.338 295.553C364.19 295.553 363.259 294.622 363.259 293.474C363.259 292.326 364.19 291.395 365.338 291.395C366.486 291.395 367.417 292.326 367.417 293.474C367.417 294.622 366.486 295.553 365.338 295.553Z"
        fill="#F5A27F"
      />
      <path
        d="M369.496 266.667C368.348 266.667 367.417 265.736 367.417 264.588C367.417 263.44 368.348 262.509 369.496 262.509C370.644 262.509 371.575 263.44 371.575 264.588C371.575 265.736 370.644 266.667 369.496 266.667Z"
        fill="#F5A27F"
      />
      <path
        d="M357.546 291.395C353.243 291.395 349.755 287.907 349.755 283.604C349.755 279.301 353.243 275.813 357.546 275.813C361.849 275.813 365.337 279.301 365.337 283.604C365.337 287.907 361.849 291.395 357.546 291.395Z"
        fill="#F5A27F"
      />
      <path
        d="M372.195 275.174C371.134 275.174 370.274 274.314 370.274 273.253C370.274 272.192 371.134 271.332 372.195 271.332C373.256 271.332 374.116 272.192 374.116 273.253C374.116 274.314 373.256 275.174 372.195 275.174Z"
        fill="#B73D2D"
      />
      <path
        d="M388.518 271.332C387.457 271.332 386.598 270.472 386.598 269.412C386.598 268.351 387.457 267.492 388.518 267.492C389.578 267.492 390.438 268.351 390.438 269.412C390.438 270.472 389.578 271.332 388.518 271.332Z"
        fill="#B73D2D"
      />
      <path
        d="M359.09 267.492C358.03 267.492 357.17 266.632 357.17 265.572C357.17 264.511 358.03 263.652 359.09 263.652C360.15 263.652 361.01 264.511 361.01 265.572C361.01 266.632 360.15 267.492 359.09 267.492Z"
        fill="#B73D2D"
      />
      <path
        d="M347.216 277.095C346.155 277.095 345.295 276.235 345.295 275.174C345.295 274.113 346.155 273.253 347.216 273.253C348.277 273.253 349.137 274.113 349.137 275.174C349.137 276.235 348.277 277.095 347.216 277.095Z"
        fill="#B73D2D"
      />
      <path
        d="M386.596 280.936C385.535 280.936 384.675 280.076 384.675 279.015C384.675 277.954 385.535 277.094 386.596 277.094C387.657 277.094 388.517 277.954 388.517 279.015C388.517 280.076 387.657 280.936 386.596 280.936Z"
        fill="#B73D2D"
      />
      <path
        d="M355.095 150.637C335.321 150.637 319.291 134.607 319.291 114.833C319.291 95.0588 335.321 79.0288 355.095 79.0288C374.869 79.0288 390.899 95.0588 390.899 114.833C390.899 134.607 374.869 150.637 355.095 150.637Z"
        fill="#D53B1C"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M333.026 143.019C328.549 136.912 325.898 129.34 325.898 121.143C325.898 100.925 342.026 84.5107 361.891 84.5107C368.882 84.5107 375.41 86.5437 380.935 90.0587C387.105 96.4927 390.899 105.223 390.899 114.832C390.899 134.593 374.856 150.636 355.096 150.636C346.773 150.636 339.109 147.79 333.026 143.019Z"
        fill="#E84827"
      />
      <path
        d="M355.095 150.637C335.321 150.637 319.291 134.607 319.291 114.833C319.291 95.0588 335.321 79.0288 355.095 79.0288C374.869 79.0288 390.899 95.0588 390.899 114.833C390.899 134.607 374.869 150.637 355.095 150.637Z"
        stroke="#8D3A2B"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M351.072 100.251C348.283 100.251 346.022 97.9898 346.022 95.2007C346.022 92.4117 348.283 90.1508 351.072 90.1508C353.861 90.1508 356.122 92.4117 356.122 95.2007C356.122 97.9898 353.861 100.251 351.072 100.251Z"
        fill="#F5A27F"
      />
      <path
        d="M367.072 137.656C362.788 137.656 359.316 134.183 359.316 129.9C359.316 125.616 362.788 122.144 367.072 122.144C371.355 122.144 374.828 125.616 374.828 129.9C374.828 134.183 371.355 137.656 367.072 137.656Z"
        fill="#F5A27F"
      />
      <path
        d="M372.122 107.797C367.954 107.797 364.576 104.418 364.576 100.251C364.576 96.0833 367.954 92.7048 372.122 92.7048C376.289 92.7048 379.668 96.0833 379.668 100.251C379.668 104.418 376.289 107.797 372.122 107.797Z"
        fill="#F5A27F"
      />
      <path
        d="M333.472 110.974C331.383 110.974 329.69 109.281 329.69 107.192C329.69 105.103 331.383 103.41 333.472 103.41C335.561 103.41 337.254 105.103 337.254 107.192C337.254 109.281 335.561 110.974 333.472 110.974Z"
        fill="#F5A27F"
      />
      <path
        d="M352.796 141.784C351.408 141.784 350.283 140.659 350.283 139.271C350.283 137.883 351.408 136.758 352.796 136.758C354.184 136.758 355.309 137.883 355.309 139.271C355.309 140.659 354.184 141.784 352.796 141.784Z"
        fill="#F5A27F"
      />
      <path
        d="M357.822 106.872C356.434 106.872 355.309 105.747 355.309 104.359C355.309 102.971 356.434 101.846 357.822 101.846C359.21 101.846 360.335 102.971 360.335 104.359C360.335 105.747 359.21 106.872 357.822 106.872Z"
        fill="#F5A27F"
      />
      <path
        d="M343.379 136.759C338.178 136.759 333.962 132.543 333.962 127.342C333.962 122.141 338.178 117.925 343.379 117.925C348.58 117.925 352.796 122.141 352.796 127.342C352.796 132.543 348.58 136.759 343.379 136.759Z"
        fill="#F5A27F"
      />
      <path
        d="M361.084 117.154C359.802 117.154 358.763 116.115 358.763 114.833C358.763 113.551 359.802 112.512 361.084 112.512C362.366 112.512 363.405 113.551 363.405 114.833C363.405 116.115 362.366 117.154 361.084 117.154Z"
        fill="#B73D2D"
      />
      <path
        d="M380.811 112.511C379.529 112.511 378.49 111.472 378.49 110.19C378.49 108.908 379.529 107.869 380.811 107.869C382.093 107.869 383.132 108.908 383.132 110.19C383.132 111.472 382.093 112.511 380.811 112.511Z"
        fill="#B73D2D"
      />
      <path
        d="M345.245 107.869C343.963 107.869 342.924 106.83 342.924 105.548C342.924 104.266 343.963 103.227 345.245 103.227C346.527 103.227 347.566 104.266 347.566 105.548C347.566 106.83 346.527 107.869 345.245 107.869Z"
        fill="#B73D2D"
      />
      <path
        d="M330.893 119.475C329.611 119.475 328.572 118.436 328.572 117.154C328.572 115.872 329.611 114.833 330.893 114.833C332.175 114.833 333.214 115.872 333.214 117.154C333.214 118.436 332.175 119.475 330.893 119.475Z"
        fill="#B73D2D"
      />
      <path
        d="M378.49 124.117C377.208 124.117 376.169 123.078 376.169 121.796C376.169 120.514 377.208 119.475 378.49 119.475C379.772 119.475 380.811 120.514 380.811 121.796C380.811 123.078 379.772 124.117 378.49 124.117Z"
        fill="#B73D2D"
      />
      <path
        d="M120.169 323.588C102.11 323.588 87.4699 308.948 87.4699 290.889C87.4699 272.83 102.11 258.19 120.169 258.19C138.228 258.19 152.868 272.83 152.868 290.889C152.868 308.948 138.228 323.588 120.169 323.588Z"
        fill="#D53B1C"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M100.013 316.631C95.9239 311.054 93.5029 304.139 93.5029 296.652C93.5029 278.188 108.232 263.197 126.375 263.197C132.761 263.197 138.724 265.054 143.77 268.266C149.404 274.142 152.868 282.114 152.868 290.889C152.868 308.936 138.216 323.588 120.169 323.588C112.567 323.588 105.568 320.989 100.013 316.631Z"
        fill="#E84827"
      />
      <path
        d="M120.169 323.588C102.11 323.588 87.4699 308.948 87.4699 290.889C87.4699 272.83 102.11 258.19 120.169 258.19C138.228 258.19 152.868 272.83 152.868 290.889C152.868 308.948 138.228 323.588 120.169 323.588Z"
        stroke="#8D3A2B"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M116.495 277.572C113.947 277.572 111.882 275.507 111.882 272.959C111.882 270.411 113.947 268.346 116.495 268.346C119.043 268.346 121.108 270.411 121.108 272.959C121.108 275.507 119.043 277.572 116.495 277.572Z"
        fill="#F5A27F"
      />
      <path
        d="M131.107 311.734C127.195 311.734 124.023 308.562 124.023 304.65C124.023 300.737 127.195 297.566 131.107 297.566C135.019 297.566 138.191 300.737 138.191 304.65C138.191 308.562 135.019 311.734 131.107 311.734Z"
        fill="#F5A27F"
      />
      <path
        d="M135.719 284.463C131.913 284.463 128.828 281.378 128.828 277.572C128.828 273.766 131.913 270.681 135.719 270.681C139.525 270.681 142.61 273.766 142.61 277.572C142.61 281.378 139.525 284.463 135.719 284.463Z"
        fill="#F5A27F"
      />
      <path
        d="M100.42 287.365C98.5123 287.365 96.9659 285.818 96.9659 283.911C96.9659 282.003 98.5123 280.457 100.42 280.457C102.327 280.457 103.874 282.003 103.874 283.911C103.874 285.818 102.327 287.365 100.42 287.365Z"
        fill="#F5A27F"
      />
      <path
        d="M118.069 315.504C116.801 315.504 115.774 314.476 115.774 313.209C115.774 311.941 116.801 310.914 118.069 310.914C119.336 310.914 120.364 311.941 120.364 313.209C120.364 314.476 119.336 315.504 118.069 315.504Z"
        fill="#F5A27F"
      />
      <path
        d="M122.659 283.619C121.391 283.619 120.364 282.591 120.364 281.324C120.364 280.056 121.391 279.029 122.659 279.029C123.926 279.029 124.954 280.056 124.954 281.324C124.954 282.591 123.926 283.619 122.659 283.619Z"
        fill="#F5A27F"
      />
      <path
        d="M109.468 310.914C104.718 310.914 100.867 307.063 100.867 302.313C100.867 297.563 104.718 293.712 109.468 293.712C114.218 293.712 118.069 297.563 118.069 302.313C118.069 307.063 114.218 310.914 109.468 310.914Z"
        fill="#F5A27F"
      />
      <path
        d="M125.637 293.009C124.466 293.009 123.517 292.06 123.517 290.889C123.517 289.718 124.466 288.769 125.637 288.769C126.808 288.769 127.757 289.718 127.757 290.889C127.757 292.06 126.808 293.009 125.637 293.009Z"
        fill="#B73D2D"
      />
      <path
        d="M143.654 288.769C142.483 288.769 141.534 287.82 141.534 286.649C141.534 285.478 142.483 284.529 143.654 284.529C144.825 284.529 145.774 285.478 145.774 286.649C145.774 287.82 144.825 288.769 143.654 288.769Z"
        fill="#B73D2D"
      />
      <path
        d="M111.172 284.529C110.001 284.529 109.052 283.58 109.052 282.409C109.052 281.238 110.001 280.289 111.172 280.289C112.343 280.289 113.292 281.238 113.292 282.409C113.292 283.58 112.343 284.529 111.172 284.529Z"
        fill="#B73D2D"
      />
      <path
        d="M98.0649 295.129C96.8941 295.129 95.9449 294.18 95.9449 293.009C95.9449 291.838 96.8941 290.889 98.0649 290.889C99.2358 290.889 100.185 291.838 100.185 293.009C100.185 294.18 99.2358 295.129 98.0649 295.129Z"
        fill="#B73D2D"
      />
      <path
        d="M141.534 299.367C140.363 299.367 139.414 298.418 139.414 297.248C139.414 296.078 140.363 295.129 141.534 295.129C142.705 295.129 143.654 296.078 143.654 297.248C143.654 298.418 142.705 299.367 141.534 299.367Z"
        fill="#B73D2D"
      />
      <path
        d="M239.924 359.39C220.15 359.39 204.121 343.36 204.121 323.587C204.121 303.813 220.15 287.784 239.924 287.784C259.697 287.784 275.727 303.813 275.727 323.587C275.727 343.36 259.697 359.39 239.924 359.39Z"
        fill="#D53B1C"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M217.854 351.774C213.377 345.667 210.726 338.095 210.726 329.898C210.726 309.68 226.854 293.266 246.719 293.266C253.71 293.266 260.239 295.299 265.764 298.816C271.934 305.249 275.727 313.979 275.727 323.588C275.727 343.348 259.684 359.391 239.924 359.391C231.601 359.391 223.937 356.545 217.854 351.774Z"
        fill="#E84827"
      />
      <path
        d="M239.924 359.39C220.15 359.39 204.121 343.36 204.121 323.587C204.121 303.813 220.15 287.784 239.924 287.784C259.697 287.784 275.727 303.813 275.727 323.587C275.727 343.36 259.697 359.39 239.924 359.39Z"
        stroke="#8D3A2B"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M235.9 309.005C233.111 309.005 230.85 306.744 230.85 303.955C230.85 301.166 233.111 298.905 235.9 298.905C238.689 298.905 240.95 301.166 240.95 303.955C240.95 306.744 238.689 309.005 235.9 309.005Z"
        fill="#F5A27F"
      />
      <path
        d="M251.9 346.412C247.616 346.412 244.144 342.939 244.144 338.656C244.144 334.372 247.616 330.9 251.9 330.9C256.183 330.9 259.656 334.372 259.656 338.656C259.656 342.939 256.183 346.412 251.9 346.412Z"
        fill="#F5A27F"
      />
      <path
        d="M256.95 316.552C252.783 316.552 249.405 313.174 249.405 309.007C249.405 304.84 252.783 301.462 256.95 301.462C261.117 301.462 264.495 304.84 264.495 309.007C264.495 313.174 261.117 316.552 256.95 316.552Z"
        fill="#F5A27F"
      />
      <path
        d="M218.299 319.73C216.21 319.73 214.517 318.037 214.517 315.948C214.517 313.859 216.21 312.166 218.299 312.166C220.388 312.166 222.081 313.859 222.081 315.948C222.081 318.037 220.388 319.73 218.299 319.73Z"
        fill="#F5A27F"
      />
      <path
        d="M237.624 350.54C236.236 350.54 235.111 349.415 235.111 348.027C235.111 346.639 236.236 345.514 237.624 345.514C239.012 345.514 240.137 346.639 240.137 348.027C240.137 349.415 239.012 350.54 237.624 350.54Z"
        fill="#F5A27F"
      />
      <path
        d="M242.65 315.628C241.262 315.628 240.137 314.503 240.137 313.115C240.137 311.727 241.262 310.602 242.65 310.602C244.038 310.602 245.163 311.727 245.163 313.115C245.163 314.503 244.038 315.628 242.65 315.628Z"
        fill="#F5A27F"
      />
      <path
        d="M228.207 345.514C223.006 345.514 218.79 341.298 218.79 336.097C218.79 330.896 223.006 326.68 228.207 326.68C233.408 326.68 237.624 330.896 237.624 336.097C237.624 341.298 233.408 345.514 228.207 345.514Z"
        fill="#F5A27F"
      />
      <path
        d="M245.912 325.909C244.63 325.909 243.591 324.87 243.591 323.588C243.591 322.306 244.63 321.267 245.912 321.267C247.194 321.267 248.233 322.306 248.233 323.588C248.233 324.87 247.194 325.909 245.912 325.909Z"
        fill="#B73D2D"
      />
      <path
        d="M265.639 321.267C264.357 321.267 263.318 320.227 263.318 318.945C263.318 317.662 264.357 316.623 265.639 316.623C266.921 316.623 267.96 317.662 267.96 318.945C267.96 320.227 266.921 321.267 265.639 321.267Z"
        fill="#B73D2D"
      />
      <path
        d="M230.073 316.624C228.791 316.624 227.752 315.585 227.752 314.303C227.752 313.021 228.791 311.982 230.073 311.982C231.355 311.982 232.394 313.021 232.394 314.303C232.394 315.585 231.355 316.624 230.073 316.624Z"
        fill="#B73D2D"
      />
      <path
        d="M215.721 328.23C214.439 328.23 213.4 327.191 213.4 325.909C213.4 324.627 214.439 323.588 215.721 323.588C217.003 323.588 218.042 324.627 218.042 325.909C218.042 327.191 217.003 328.23 215.721 328.23Z"
        fill="#B73D2D"
      />
      <path
        d="M263.318 332.872C262.036 332.872 260.997 331.833 260.997 330.551C260.997 329.269 262.036 328.23 263.318 328.23C264.6 328.23 265.639 329.269 265.639 330.551C265.639 331.833 264.6 332.872 263.318 332.872Z"
        fill="#B73D2D"
      />
      <path
        d="M422.589 391.401C402.815 391.401 386.785 375.371 386.785 355.597C386.785 335.823 402.815 319.793 422.589 319.793C442.363 319.793 458.393 335.823 458.393 355.597C458.393 375.371 442.363 391.401 422.589 391.401Z"
        fill="#D53B1C"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M400.518 383.783C396.042 377.676 393.391 370.105 393.391 361.909C393.391 341.69 409.519 325.276 429.384 325.276C436.375 325.276 442.904 327.309 448.429 330.826C454.599 337.259 458.392 345.989 458.392 355.598C458.392 375.358 442.349 391.401 422.589 391.401C414.265 391.401 406.601 388.554 400.518 383.783Z"
        fill="#E84827"
      />
      <path
        d="M422.589 391.401C402.815 391.401 386.785 375.371 386.785 355.597C386.785 335.823 402.815 319.793 422.589 319.793C442.363 319.793 458.393 335.823 458.393 355.597C458.393 375.371 442.363 391.401 422.589 391.401Z"
        stroke="#8D3A2B"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M418.565 341.016C415.776 341.016 413.515 338.755 413.515 335.966C413.515 333.177 415.776 330.916 418.565 330.916C421.354 330.916 423.615 333.177 423.615 335.966C423.615 338.755 421.354 341.016 418.565 341.016Z"
        fill="#F5A27F"
      />
      <path
        d="M434.565 378.422C430.281 378.422 426.809 374.949 426.809 370.666C426.809 366.382 430.281 362.91 434.565 362.91C438.848 362.91 442.321 366.382 442.321 370.666C442.321 374.949 438.848 378.422 434.565 378.422Z"
        fill="#F5A27F"
      />
      <path
        d="M439.615 348.563C435.447 348.563 432.069 345.184 432.069 341.017C432.069 336.849 435.447 333.471 439.615 333.471C443.782 333.471 447.161 336.849 447.161 341.017C447.161 345.184 443.782 348.563 439.615 348.563Z"
        fill="#F5A27F"
      />
      <path
        d="M400.965 351.74C398.876 351.74 397.183 350.047 397.183 347.958C397.183 345.869 398.876 344.176 400.965 344.176C403.054 344.176 404.747 345.869 404.747 347.958C404.747 350.047 403.054 351.74 400.965 351.74Z"
        fill="#F5A27F"
      />
      <path
        d="M420.29 382.55C418.902 382.55 417.777 381.425 417.777 380.037C417.777 378.649 418.902 377.524 420.29 377.524C421.678 377.524 422.803 378.649 422.803 380.037C422.803 381.425 421.678 382.55 420.29 382.55Z"
        fill="#F5A27F"
      />
      <path
        d="M425.315 347.638C423.927 347.638 422.802 346.513 422.802 345.125C422.802 343.737 423.927 342.612 425.315 342.612C426.703 342.612 427.828 343.737 427.828 345.125C427.828 346.513 426.703 347.638 425.315 347.638Z"
        fill="#F5A27F"
      />
      <path
        d="M410.873 377.524C405.672 377.524 401.456 373.308 401.456 368.107C401.456 362.906 405.672 358.69 410.873 358.69C416.074 358.69 420.29 362.906 420.29 368.107C420.29 373.308 416.074 377.524 410.873 377.524Z"
        fill="#F5A27F"
      />
      <path
        d="M428.577 357.919C427.295 357.919 426.256 356.88 426.256 355.598C426.256 354.316 427.295 353.277 428.577 353.277C429.859 353.277 430.898 354.316 430.898 355.598C430.898 356.88 429.859 357.919 428.577 357.919Z"
        fill="#B73D2D"
      />
      <path
        d="M448.304 353.278C447.022 353.278 445.983 352.238 445.983 350.956C445.983 349.673 447.022 348.634 448.304 348.634C449.586 348.634 450.625 349.673 450.625 350.956C450.625 352.238 449.586 353.278 448.304 353.278Z"
        fill="#B73D2D"
      />
      <path
        d="M412.738 348.634C411.456 348.634 410.417 347.595 410.417 346.313C410.417 345.031 411.456 343.992 412.738 343.992C414.02 343.992 415.059 345.031 415.059 346.313C415.059 347.595 414.02 348.634 412.738 348.634Z"
        fill="#B73D2D"
      />
      <path
        d="M398.387 360.24C397.105 360.24 396.066 359.201 396.066 357.919C396.066 356.637 397.105 355.598 398.387 355.598C399.669 355.598 400.708 356.637 400.708 357.919C400.708 359.201 399.669 360.24 398.387 360.24Z"
        fill="#B73D2D"
      />
      <path
        d="M445.983 364.882C444.701 364.882 443.662 363.843 443.662 362.561C443.662 361.279 444.701 360.24 445.983 360.24C447.265 360.24 448.304 361.279 448.304 362.561C448.304 363.843 447.265 364.882 445.983 364.882Z"
        fill="#B73D2D"
      />
      <path
        d="M472.815 307.355C460.665 307.355 450.815 297.505 450.815 285.355C450.815 273.205 460.665 263.355 472.815 263.355C484.965 263.355 494.815 273.205 494.815 285.355C494.815 297.505 484.965 307.355 472.815 307.355Z"
        fill="#D53B1C"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M459.254 302.672C456.504 298.921 454.875 294.268 454.875 289.232C454.875 276.809 464.785 266.723 476.991 266.723C481.287 266.723 485.298 267.972 488.693 270.133C492.484 274.086 494.815 279.45 494.815 285.354C494.815 297.496 484.958 307.354 472.816 307.354C467.701 307.354 462.992 305.605 459.254 302.672Z"
        fill="#E84827"
      />
      <path
        d="M472.815 307.355C460.665 307.355 450.815 297.505 450.815 285.355C450.815 273.205 460.665 263.355 472.815 263.355C484.965 263.355 494.815 273.205 494.815 285.355C494.815 297.505 484.965 307.355 472.815 307.355Z"
        stroke="#8D3A2B"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M470.343 276.395C468.629 276.395 467.24 275.006 467.24 273.292C467.24 271.578 468.629 270.189 470.343 270.189C472.057 270.189 473.446 271.578 473.446 273.292C473.446 275.006 472.057 276.395 470.343 276.395Z"
        fill="#F5A27F"
      />
      <path
        d="M480.175 299.379C477.543 299.379 475.409 297.245 475.409 294.613C475.409 291.981 477.543 289.847 480.175 289.847C482.807 289.847 484.941 291.981 484.941 294.613C484.941 297.245 482.807 299.379 480.175 299.379Z"
        fill="#F5A27F"
      />
      <path
        d="M483.278 281.031C480.718 281.031 478.642 278.955 478.642 276.395C478.642 273.834 480.718 271.759 483.278 271.759C485.838 271.759 487.914 273.834 487.914 276.395C487.914 278.955 485.838 281.031 483.278 281.031Z"
        fill="#F5A27F"
      />
      <path
        d="M459.528 282.984C458.244 282.984 457.204 281.943 457.204 280.66C457.204 279.376 458.244 278.336 459.528 278.336C460.811 278.336 461.852 279.376 461.852 280.66C461.852 281.943 460.811 282.984 459.528 282.984Z"
        fill="#F5A27F"
      />
      <path
        d="M471.403 301.915C470.55 301.915 469.859 301.224 469.859 300.371C469.859 299.518 470.55 298.827 471.403 298.827C472.256 298.827 472.947 299.518 472.947 300.371C472.947 301.224 472.256 301.915 471.403 301.915Z"
        fill="#F5A27F"
      />
      <path
        d="M474.491 280.464C473.638 280.464 472.947 279.772 472.947 278.92C472.947 278.067 473.638 277.376 474.491 277.376C475.344 277.376 476.035 278.067 476.035 278.92C476.035 279.772 475.344 280.464 474.491 280.464Z"
        fill="#F5A27F"
      />
      <path
        d="M465.617 298.827C462.421 298.827 459.831 296.236 459.831 293.041C459.831 289.845 462.421 287.255 465.617 287.255C468.812 287.255 471.403 289.845 471.403 293.041C471.403 296.236 468.812 298.827 465.617 298.827Z"
        fill="#F5A27F"
      />
      <path
        d="M476.495 286.781C475.707 286.781 475.069 286.142 475.069 285.354C475.069 284.566 475.707 283.927 476.495 283.927C477.282 283.927 477.921 284.566 477.921 285.354C477.921 286.142 477.282 286.781 476.495 286.781Z"
        fill="#B73D2D"
      />
      <path
        d="M488.617 283.928C487.829 283.928 487.19 283.289 487.19 282.502C487.19 281.714 487.829 281.076 488.617 281.076C489.405 281.076 490.044 281.714 490.044 282.502C490.044 283.289 489.405 283.928 488.617 283.928Z"
        fill="#B73D2D"
      />
      <path
        d="M466.763 281.077C465.975 281.077 465.337 280.438 465.337 279.65C465.337 278.862 465.975 278.223 466.763 278.223C467.55 278.223 468.189 278.862 468.189 279.65C468.189 280.438 467.55 281.077 466.763 281.077Z"
        fill="#B73D2D"
      />
      <path
        d="M457.945 288.208C457.157 288.208 456.518 287.569 456.518 286.781C456.518 285.993 457.157 285.354 457.945 285.354C458.733 285.354 459.372 285.993 459.372 286.781C459.372 287.569 458.733 288.208 457.945 288.208Z"
        fill="#B73D2D"
      />
      <path
        d="M487.19 291.059C486.402 291.059 485.763 290.42 485.763 289.633C485.763 288.845 486.402 288.207 487.19 288.207C487.978 288.207 488.617 288.845 488.617 289.633C488.617 290.42 487.978 291.059 487.19 291.059Z"
        fill="#B73D2D"
      />
      <path
        d="M239.924 475.15C216.859 475.15 198.161 456.452 198.161 433.387C198.161 410.322 216.859 391.624 239.924 391.624C262.989 391.624 281.687 410.322 281.687 433.387C281.687 456.452 262.989 475.15 239.924 475.15Z"
        fill="#D53B1C"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M214.18 466.264C208.958 459.141 205.866 450.31 205.866 440.748C205.866 417.165 224.679 398.019 247.85 398.019C256.005 398.019 263.621 400.39 270.066 404.492C277.262 411.996 281.687 422.179 281.687 433.387C281.687 456.437 262.973 475.15 239.924 475.15C230.215 475.15 221.275 471.83 214.18 466.264Z"
        fill="#E84827"
      />
      <path
        d="M239.924 475.15C216.859 475.15 198.161 456.452 198.161 433.387C198.161 410.322 216.859 391.624 239.924 391.624C262.989 391.624 281.687 410.322 281.687 433.387C281.687 456.452 262.989 475.15 239.924 475.15Z"
        stroke="#8D3A2B"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M235.231 416.378C231.977 416.378 229.34 413.74 229.34 410.487C229.34 407.233 231.977 404.596 235.231 404.596C238.484 404.596 241.122 407.233 241.122 410.487C241.122 413.74 238.484 416.378 235.231 416.378Z"
        fill="#F5A27F"
      />
      <path
        d="M253.894 460.01C248.897 460.01 244.847 455.959 244.847 450.963C244.847 445.966 248.897 441.916 253.894 441.916C258.89 441.916 262.941 445.966 262.941 450.963C262.941 455.959 258.89 460.01 253.894 460.01Z"
        fill="#F5A27F"
      />
      <path
        d="M259.785 425.18C254.924 425.18 250.984 421.239 250.984 416.379C250.984 411.518 254.924 407.578 259.785 407.578C264.646 407.578 268.586 411.518 268.586 416.379C268.586 421.239 264.646 425.18 259.785 425.18Z"
        fill="#F5A27F"
      />
      <path
        d="M214.701 428.888C212.264 428.888 210.289 426.912 210.289 424.476C210.289 422.039 212.264 420.064 214.701 420.064C217.138 420.064 219.113 422.039 219.113 424.476C219.113 426.912 217.138 428.888 214.701 428.888Z"
        fill="#F5A27F"
      />
      <path
        d="M237.242 464.825C235.623 464.825 234.311 463.513 234.311 461.894C234.311 460.275 235.623 458.963 237.242 458.963C238.861 458.963 240.173 460.275 240.173 461.894C240.173 463.513 238.861 464.825 237.242 464.825Z"
        fill="#F5A27F"
      />
      <path
        d="M243.104 424.102C241.485 424.102 240.173 422.789 240.173 421.171C240.173 419.552 241.485 418.24 243.104 418.24C244.723 418.24 246.035 419.552 246.035 421.171C246.035 422.789 244.723 424.102 243.104 424.102Z"
        fill="#F5A27F"
      />
      <path
        d="M226.257 458.963C220.19 458.963 215.272 454.045 215.272 447.979C215.272 441.912 220.19 436.995 226.257 436.995C232.324 436.995 237.242 441.912 237.242 447.979C237.242 454.045 232.324 458.963 226.257 458.963Z"
        fill="#F5A27F"
      />
      <path
        d="M246.908 436.095C245.413 436.095 244.201 434.883 244.201 433.388C244.201 431.893 245.413 430.681 246.908 430.681C248.403 430.681 249.615 431.893 249.615 433.388C249.615 434.883 248.403 436.095 246.908 436.095Z"
        fill="#B73D2D"
      />
      <path
        d="M269.92 430.681C268.424 430.681 267.212 429.468 267.212 427.973C267.212 426.477 268.424 425.265 269.92 425.265C271.416 425.265 272.628 426.477 272.628 427.973C272.628 429.468 271.416 430.681 269.92 430.681Z"
        fill="#B73D2D"
      />
      <path
        d="M228.434 425.265C226.939 425.265 225.727 424.053 225.727 422.558C225.727 421.063 226.939 419.851 228.434 419.851C229.929 419.851 231.141 421.063 231.141 422.558C231.141 424.053 229.929 425.265 228.434 425.265Z"
        fill="#B73D2D"
      />
      <path
        d="M211.692 438.802C210.197 438.802 208.985 437.59 208.985 436.095C208.985 434.6 210.197 433.388 211.692 433.388C213.187 433.388 214.399 434.6 214.399 436.095C214.399 437.59 213.187 438.802 211.692 438.802Z"
        fill="#B73D2D"
      />
      <path
        d="M267.211 444.217C265.716 444.217 264.504 443.005 264.504 441.51C264.504 440.015 265.716 438.803 267.211 438.803C268.706 438.803 269.918 440.015 269.918 441.51C269.918 443.005 268.706 444.217 267.211 444.217Z"
        fill="#B73D2D"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M83.1774 188.347C83.1774 188.347 91.1947 216.836 99.2287 223.519C106.347 229.44 116.933 228.469 122.853 221.349C128.775 214.231 127.803 203.645 120.684 197.724C112.651 191.042 83.1774 188.347 83.1774 188.347Z"
        fill="#94E281"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M83.2629 188.647C83.2069 188.451 83.1769 188.347 83.1769 188.347C83.1769 188.347 112.651 191.042 120.685 197.724C127.803 203.645 128.775 214.231 122.854 221.349C122.817 221.394 122.779 221.439 122.741 221.483L83.2629 188.647Z"
        fill="#4BB74C"
      />
      <path
        d="M83.1774 188.347C83.1774 188.347 91.1947 216.836 99.2287 223.519C106.347 229.44 116.933 228.469 122.853 221.349C128.775 214.231 127.803 203.645 120.684 197.724C112.651 191.042 83.1774 188.347 83.1774 188.347Z"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M122.854 221.348L132.92 229.722"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M101.148 203.295L122.854 221.349"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M101.062 213.004L111.652 212.031"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M108.699 219.356L119.289 218.384"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M110.679 201.442L111.652 212.031"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M118.316 207.794L119.289 218.384"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M324.389 148.623C324.389 148.623 294.505 170.91 290.272 183.373C286.522 194.415 292.443 206.425 303.487 210.175C314.53 213.925 326.54 208.006 330.29 196.963C334.521 184.499 324.389 148.623 324.389 148.623Z"
        fill="#94E281"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M324.075 148.86C324.28 148.705 324.389 148.624 324.389 148.624C324.389 148.624 334.522 184.499 330.29 196.963C326.54 208.006 314.53 213.926 303.487 210.176C303.417 210.152 303.348 210.128 303.278 210.104L324.075 148.86Z"
        fill="#4BB74C"
      />
      <path
        d="M324.389 148.623C324.389 148.623 294.505 170.91 290.272 183.373C286.522 194.415 292.443 206.425 303.487 210.175C314.53 213.925 326.54 208.006 330.29 196.963C334.521 184.499 324.389 148.623 324.389 148.623Z"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M303.487 210.176L298.184 225.791"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M314.921 176.502L303.488 210.175"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M303.467 180.783L309.39 192.797"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M299.443 192.631L305.365 204.645"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M321.402 186.874L309.39 192.797"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M317.378 198.722L305.365 204.645"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M519.787 217.031C519.787 217.031 487.407 210.404 476.663 214.957C467.143 218.99 462.689 229.994 466.723 239.515C470.757 249.034 481.761 253.489 491.281 249.454C502.026 244.902 519.787 217.031 519.787 217.031Z"
        fill="#94E281"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M519.446 216.963C519.669 217.007 519.786 217.031 519.786 217.031C519.786 217.031 502.025 244.902 491.281 249.455C481.761 253.489 470.757 249.035 466.723 239.515C466.697 239.455 466.672 239.394 466.647 239.334L519.446 216.963Z"
        fill="#4BB74C"
      />
      <path
        d="M519.787 217.031C519.787 217.031 487.407 210.404 476.663 214.957C467.143 218.99 462.689 229.994 466.723 239.515C470.757 249.034 481.761 253.489 491.281 249.454C502.026 244.902 519.787 217.031 519.787 217.031Z"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M466.723 239.515L453.26 245.219"
        stroke="#3F6B29"
        strokeWidth="5.17"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M495.752 227.215L466.723 239.516"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M486.16 222.159L481.704 233.166"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M475.947 226.488L471.491 237.494"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M492.716 237.622L481.709 233.166"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M482.498 241.95L471.491 237.495"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M266.791 509.72C266.791 509.72 287.805 490.366 290.045 480.531C292.028 471.817 286.565 463.132 277.851 461.147C269.137 459.163 260.451 464.627 258.467 473.341C256.227 483.176 266.791 509.72 266.791 509.72Z"
        fill="#94E281"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M267.011 509.515C266.867 509.649 266.791 509.72 266.791 509.72C266.791 509.72 256.227 483.176 258.467 473.341C260.451 464.627 269.137 459.163 277.851 461.147C277.906 461.16 277.961 461.172 278.016 461.186L267.011 509.515Z"
        fill="#4BB74C"
      />
      <path
        d="M266.791 509.72C266.791 509.72 287.805 490.366 290.045 480.531C292.028 471.817 286.565 463.132 277.851 461.147C269.137 459.163 260.451 464.627 258.467 473.341C256.227 483.176 266.791 509.72 266.791 509.72Z"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M277.851 461.147L280.657 448.825"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M271.8 487.719L277.851 461.147"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M280.193 483.549L274.728 474.862"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M282.322 474.201L276.857 465.512"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M266.04 480.327L274.728 474.862"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M268.169 470.978L276.857 465.512"
        stroke="#3F6B29"
        strokeWidth="5.18"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M267.355 175.999C242.663 180.625 218.896 164.359 214.27 139.666C209.644 114.974 225.911 91.2071 250.603 86.5811C275.295 81.9551 299.062 98.2219 303.688 122.914C308.314 147.606 292.047 171.373 267.355 175.999Z"
        fill="#F16A65"
        stroke="#C72828"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M259.483 139.301C259.633 138.934 259.958 138.668 260.348 138.595C260.737 138.522 261.137 138.652 261.409 138.94C265.252 143.005 276.929 155.357 281.061 159.727C281.354 160.037 281.454 160.483 281.322 160.888C281.19 161.294 280.847 161.595 280.428 161.674C274.247 162.832 256.165 166.219 249.983 167.377C249.564 167.456 249.135 167.299 248.866 166.969C248.596 166.638 248.528 166.187 248.689 165.792C250.959 160.222 257.372 144.482 259.483 139.301Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M251.856 134.657C252.249 134.603 252.642 134.752 252.9 135.053C253.158 135.354 253.245 135.765 253.132 136.145C251.533 141.506 246.674 157.793 244.955 163.557C244.833 163.966 244.498 164.275 244.081 164.364C243.664 164.452 243.231 164.306 242.954 163.982C238.86 159.208 226.885 145.242 222.791 140.468C222.514 140.144 222.435 139.695 222.586 139.296C222.738 138.897 223.095 138.613 223.517 138.555C229.476 137.735 246.314 135.419 251.856 134.657Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M258.377 122.753C258.227 123.12 257.901 123.386 257.512 123.459C257.122 123.532 256.723 123.402 256.45 123.114C252.607 119.049 240.931 106.697 236.799 102.327C236.506 102.017 236.406 101.572 236.538 101.166C236.669 100.76 237.012 100.459 237.431 100.381C243.613 99.2218 261.695 95.8348 267.877 94.6768C268.296 94.5978 268.724 94.7547 268.994 95.0847C269.264 95.4157 269.332 95.8667 269.171 96.2617C266.901 101.832 260.488 117.572 258.377 122.753Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M265.634 125.425C265.242 125.479 264.848 125.33 264.591 125.029C264.333 124.728 264.245 124.317 264.358 123.937C265.957 118.576 270.816 102.288 272.535 96.5248C272.657 96.1158 272.992 95.8068 273.409 95.7178C273.827 95.6288 274.259 95.7758 274.537 96.0988C278.63 100.874 290.605 114.84 294.699 119.614C294.976 119.938 295.055 120.387 294.904 120.786C294.752 121.185 294.395 121.469 293.973 121.527C288.015 122.347 271.176 124.663 265.634 125.425Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M251.303 126.383C251.546 126.696 251.613 127.111 251.482 127.485C251.35 127.858 251.038 128.14 250.652 128.232C245.21 129.527 228.675 133.464 222.825 134.857C222.41 134.955 221.974 134.82 221.688 134.503C221.403 134.186 221.314 133.738 221.455 133.336C223.543 127.403 229.65 110.05 231.738 104.118C231.88 103.715 232.23 103.422 232.651 103.354C233.072 103.286 233.497 103.453 233.758 103.79C237.447 108.54 247.872 121.965 251.303 126.383Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M266.187 133.699C265.944 133.386 265.877 132.971 266.008 132.597C266.14 132.223 266.452 131.942 266.838 131.85C272.28 130.555 288.815 126.618 294.666 125.225C295.081 125.127 295.516 125.262 295.802 125.579C296.087 125.896 296.177 126.344 296.035 126.746C293.947 132.678 287.84 150.032 285.752 155.964C285.61 156.367 285.26 156.659 284.839 156.728C284.418 156.796 283.994 156.629 283.732 156.292C280.043 151.542 269.618 138.117 266.187 133.699Z"
        fill="#C72828"
      />
      <path
        d="M256.683 119.035C255.055 119.34 253.338 117.47 252.849 114.858C252.36 112.247 253.284 109.882 254.912 109.578C256.54 109.273 258.256 111.143 258.745 113.754C259.234 116.366 258.311 118.73 256.683 119.035Z"
        fill="#F3C553"
      />
      <path
        d="M262.579 150.503C260.951 150.808 259.234 148.938 258.745 146.327C258.255 143.715 259.178 141.351 260.806 141.046C262.434 140.741 264.151 142.61 264.64 145.222C265.13 147.834 264.206 150.198 262.579 150.503Z"
        fill="#F3C553"
      />
      <path
        d="M268.468 121.806C267.389 120.548 268.151 118.127 270.168 116.398C272.185 114.668 274.694 114.285 275.772 115.543C276.85 116.8 276.089 119.221 274.072 120.951C272.055 122.68 269.546 123.063 268.468 121.806Z"
        fill="#F3C553"
      />
      <path
        d="M242.26 144.259C241.181 143.001 241.943 140.58 243.96 138.85C245.977 137.121 248.486 136.738 249.564 137.995C250.642 139.253 249.881 141.674 247.864 143.403C245.847 145.133 243.338 145.516 242.26 144.259Z"
        fill="#F3C553"
      />
      <path
        d="M238.199 122.582C238.749 121.019 241.227 120.468 243.733 121.35C246.239 122.232 247.825 124.214 247.276 125.776C246.726 127.338 244.248 127.89 241.742 127.008C239.235 126.126 237.649 124.144 238.199 122.582Z"
        fill="#F3C553"
      />
      <path
        d="M270.757 134.026C271.307 132.463 273.785 131.912 276.291 132.794C278.797 133.676 280.384 135.658 279.834 137.22C279.284 138.782 276.806 139.334 274.3 138.452C271.794 137.57 270.208 135.588 270.757 134.026Z"
        fill="#F3C553"
      />
      <path
        d="M359.724 471.535C335.033 476.161 311.266 459.895 306.64 435.203C302.014 410.512 318.281 386.745 342.973 382.119C367.664 377.493 391.431 393.76 396.057 418.451C400.682 443.143 384.416 466.909 359.724 471.535Z"
        fill="#F16A65"
        stroke="#C72828"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M351.852 434.838C352.002 434.471 352.327 434.205 352.717 434.132C353.106 434.059 353.506 434.189 353.778 434.477C357.621 438.542 369.298 450.893 373.43 455.264C373.723 455.574 373.823 456.019 373.691 456.425C373.559 456.83 373.217 457.131 372.797 457.21C366.616 458.368 348.534 461.756 342.352 462.914C341.933 462.992 341.504 462.836 341.235 462.505C340.965 462.175 340.897 461.723 341.058 461.328C343.328 455.759 349.741 440.018 351.852 434.838Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M344.225 430.193C344.618 430.139 345.011 430.289 345.269 430.589C345.527 430.89 345.614 431.302 345.501 431.681C343.902 437.042 339.044 453.33 337.324 459.093C337.203 459.502 336.867 459.811 336.45 459.9C336.033 459.989 335.6 459.843 335.323 459.519C331.229 454.744 319.254 440.779 315.161 436.004C314.883 435.681 314.804 435.231 314.956 434.832C315.107 434.433 315.464 434.149 315.887 434.091C321.845 433.272 338.683 430.956 344.225 430.193Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M350.746 418.289C350.596 418.656 350.271 418.922 349.881 418.995C349.492 419.068 349.092 418.938 348.82 418.65C344.977 414.585 333.3 402.234 329.168 397.863C328.875 397.553 328.775 397.108 328.907 396.702C329.039 396.297 329.381 395.995 329.801 395.917C335.982 394.759 354.064 391.371 360.246 390.213C360.665 390.135 361.094 390.291 361.363 390.622C361.633 390.952 361.701 391.404 361.54 391.798C359.27 397.368 352.857 413.109 350.746 418.289Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M358.003 420.961C357.611 421.015 357.218 420.866 356.96 420.565C356.702 420.264 356.614 419.853 356.728 419.473C358.327 414.113 363.185 397.825 364.904 392.061C365.026 391.653 365.361 391.343 365.779 391.255C366.196 391.166 366.628 391.312 366.906 391.636C370.999 396.41 382.974 410.376 387.068 415.15C387.346 415.474 387.424 415.924 387.273 416.322C387.122 416.721 386.764 417.005 386.342 417.064C380.384 417.883 363.545 420.199 358.003 420.961Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M343.672 421.919C343.915 422.232 343.982 422.647 343.851 423.021C343.719 423.395 343.407 423.676 343.021 423.768C337.579 425.064 321.044 429 315.194 430.393C314.779 430.492 314.343 430.356 314.058 430.039C313.772 429.722 313.683 429.275 313.824 428.872C315.912 422.94 322.019 405.586 324.107 399.654C324.249 399.252 324.599 398.959 325.02 398.89C325.441 398.822 325.866 398.989 326.127 399.326C329.816 404.076 340.241 417.501 343.672 421.919Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M358.556 429.235C358.313 428.922 358.246 428.507 358.378 428.134C358.509 427.76 358.822 427.478 359.207 427.387C364.649 426.091 381.184 422.155 387.035 420.762C387.45 420.663 387.885 420.799 388.171 421.116C388.456 421.433 388.546 421.88 388.404 422.282C386.316 428.215 380.209 445.568 378.121 451.501C377.98 451.903 377.63 452.196 377.209 452.264C376.788 452.332 376.363 452.165 376.101 451.829C372.412 447.078 361.988 433.654 358.556 429.235Z"
        fill="#C72828"
      />
      <path
        d="M349.053 414.572C347.425 414.877 345.708 413.007 345.219 410.395C344.73 407.784 345.654 405.419 347.282 405.114C348.91 404.81 350.626 406.68 351.115 409.291C351.604 411.903 350.681 414.267 349.053 414.572Z"
        fill="#F3C553"
      />
      <path
        d="M354.948 446.039C353.32 446.344 351.603 444.474 351.114 441.863C350.625 439.251 351.547 436.887 353.175 436.582C354.803 436.277 356.52 438.146 357.009 440.758C357.499 443.37 356.576 445.734 354.948 446.039Z"
        fill="#F3C553"
      />
      <path
        d="M360.837 417.343C359.759 416.085 360.52 413.664 362.537 411.934C364.554 410.205 367.063 409.822 368.141 411.079C369.22 412.337 368.458 414.758 366.441 416.488C364.424 418.217 361.915 418.6 360.837 417.343Z"
        fill="#F3C553"
      />
      <path
        d="M334.629 439.795C333.551 438.538 334.312 436.117 336.329 434.387C338.346 432.658 340.855 432.275 341.933 433.532C343.011 434.789 342.25 437.211 340.233 438.94C338.216 440.67 335.707 441.053 334.629 439.795Z"
        fill="#F3C553"
      />
      <path
        d="M330.568 418.118C331.118 416.556 333.596 416.004 336.102 416.887C338.608 417.769 340.194 419.75 339.645 421.313C339.095 422.875 336.617 423.426 334.111 422.544C331.605 421.662 330.018 419.681 330.568 418.118Z"
        fill="#F3C553"
      />
      <path
        d="M363.125 429.561C363.675 427.999 366.153 427.447 368.659 428.329C371.165 429.211 372.752 431.193 372.202 432.755C371.652 434.318 369.174 434.869 366.668 433.987C364.162 433.105 362.576 431.124 363.125 429.561Z"
        fill="#F3C553"
      />
      <path
        d="M402.943 226.607C387.566 213.729 385.541 190.825 398.418 175.449C411.296 160.073 434.2 158.047 449.576 170.925C464.953 183.803 466.979 206.707 454.101 222.083C441.224 237.459 418.319 239.485 402.943 226.607Z"
        fill="#F16A65"
        stroke="#C72828"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M421.228 203.248C421.606 203.13 422.019 203.213 422.323 203.467C422.626 203.722 422.78 204.113 422.731 204.506C422.15 209.128 420.605 221.416 419.981 226.376C419.928 226.8 419.648 227.16 419.251 227.316C418.854 227.472 418.403 227.398 418.076 227.124C414.065 223.765 403.789 215.159 399.778 211.8C399.451 211.526 399.299 211.095 399.383 210.677C399.467 210.259 399.773 209.92 400.18 209.793C404.953 208.309 416.78 204.631 421.228 203.248Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M420.303 195.834C420.594 196.103 420.729 196.501 420.661 196.891C420.592 197.282 420.33 197.61 419.965 197.764C415.672 199.572 404.257 204.378 399.65 206.318C399.257 206.483 398.805 206.421 398.471 206.155C398.138 205.889 397.976 205.463 398.05 205.042C398.953 199.889 401.268 186.686 402.172 181.533C402.246 181.113 402.543 180.767 402.947 180.63C403.351 180.494 403.798 180.589 404.111 180.878C407.783 184.27 416.882 192.673 420.303 195.834Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M431.567 193.955C431.188 194.073 430.776 193.99 430.472 193.736C430.169 193.481 430.015 193.09 430.064 192.697C430.645 188.075 432.19 175.787 432.814 170.827C432.867 170.404 433.147 170.043 433.544 169.887C433.941 169.731 434.392 169.805 434.719 170.079C438.73 173.438 449.006 182.044 453.017 185.404C453.344 185.677 453.496 186.108 453.412 186.526C453.328 186.944 453.022 187.283 452.615 187.41C447.842 188.894 436.015 192.572 431.567 193.955Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M433.52 200.141C433.229 199.872 433.095 199.474 433.163 199.084C433.231 198.693 433.494 198.365 433.859 198.211C438.152 196.403 449.566 191.597 454.174 189.657C454.567 189.491 455.019 189.554 455.352 189.82C455.686 190.085 455.848 190.512 455.774 190.932C454.87 196.085 452.555 209.289 451.651 214.442C451.578 214.862 451.281 215.208 450.876 215.345C450.472 215.481 450.026 215.386 449.713 215.097C446.04 211.705 436.942 203.302 433.52 200.141Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M425.473 191.187C425.385 191.574 425.108 191.89 424.735 192.026C424.363 192.161 423.947 192.099 423.632 191.86C419.92 189.046 410.05 181.563 406.066 178.543C405.726 178.286 405.554 177.863 405.618 177.441C405.681 177.019 405.97 176.666 406.371 176.52C411.285 174.726 423.877 170.129 428.792 168.335C429.193 168.189 429.641 168.273 429.961 168.555C430.282 168.836 430.422 169.271 430.328 169.687C429.227 174.563 426.499 186.644 425.473 191.187Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M428.351 204.787C428.438 204.401 428.716 204.085 429.088 203.949C429.46 203.813 429.876 203.876 430.192 204.115C433.904 206.929 443.774 214.412 447.757 217.432C448.097 217.689 448.269 218.112 448.206 218.534C448.142 218.956 447.853 219.309 447.453 219.455C442.538 221.249 429.946 225.846 425.032 227.64C424.631 227.786 424.183 227.702 423.862 227.42C423.542 227.138 423.401 226.704 423.495 226.288C424.596 221.412 427.325 209.331 428.351 204.787Z"
        fill="#C72828"
      />
      <path
        d="M432.652 191.135C431.638 190.286 431.921 188.279 433.283 186.653C434.645 185.027 436.571 184.396 437.584 185.245C438.598 186.094 438.316 188.101 436.954 189.727C435.592 191.353 433.666 191.984 432.652 191.135Z"
        fill="#F3C553"
      />
      <path
        d="M416.241 210.73C415.227 209.881 415.509 207.874 416.871 206.248C418.233 204.622 420.159 203.991 421.173 204.84C422.187 205.689 421.904 207.696 420.542 209.322C419.18 210.948 417.254 211.579 416.241 210.73Z"
        fill="#F3C553"
      />
      <path
        d="M436.921 199.805C437.149 198.502 439.028 197.743 441.118 198.11C443.207 198.476 444.716 199.829 444.487 201.132C444.259 202.435 442.38 203.194 440.29 202.828C438.201 202.461 436.692 201.108 436.921 199.805Z"
        fill="#F3C553"
      />
      <path
        d="M409.783 195.035C410.012 193.732 411.891 192.973 413.98 193.339C416.07 193.706 417.578 195.059 417.35 196.362C417.121 197.665 415.242 198.424 413.153 198.057C411.063 197.691 409.555 196.338 409.783 195.035Z"
        fill="#F3C553"
      />
      <path
        d="M421.089 181.535C422.331 181.082 423.927 182.33 424.655 184.323C425.382 186.315 424.965 188.298 423.723 188.751C422.481 189.205 420.884 187.957 420.157 185.964C419.43 183.972 419.847 181.989 421.089 181.535Z"
        fill="#F3C553"
      />
      <path
        d="M430.548 207.415C431.79 206.961 433.387 208.209 434.115 210.201C434.842 212.194 434.425 214.177 433.182 214.631C431.939 215.084 430.343 213.837 429.615 211.844C428.888 209.851 429.305 207.868 430.548 207.415Z"
        fill="#F3C553"
      />
      <path
        d="M165.966 423.409C144.291 418.016 131.093 396.073 136.487 374.398C141.88 352.724 163.823 339.526 185.498 344.919C207.172 350.313 220.371 372.256 214.977 393.93C209.583 415.605 187.64 428.803 165.966 423.409Z"
        fill="#F16A65"
        stroke="#C72828"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M173.03 390.964C173.319 390.693 173.725 390.586 174.11 390.682C174.495 390.778 174.804 391.062 174.932 391.437C176.573 396.258 181.226 409.921 182.989 415.099C183.127 415.502 183.033 415.949 182.744 416.263C182.455 416.577 182.018 416.708 181.604 416.605C176.052 415.223 160.892 411.451 155.34 410.069C154.926 409.966 154.601 409.645 154.494 409.232C154.386 408.82 154.512 408.381 154.823 408.089C158.807 404.342 169.321 394.453 173.03 390.964Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M168.536 384.193C168.915 384.307 169.21 384.606 169.32 384.987C169.429 385.368 169.338 385.778 169.077 386.076C165.723 389.908 156.217 400.769 152.614 404.885C152.333 405.206 151.9 405.348 151.483 405.255C151.067 405.162 150.735 404.849 150.617 404.439C149.037 398.939 144.725 383.924 143.145 378.425C143.027 378.015 143.143 377.573 143.447 377.274C143.75 376.974 144.193 376.864 144.601 376.987C149.839 378.564 163.66 382.725 168.536 384.193Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M178.549 376.901C178.261 377.172 177.854 377.278 177.469 377.183C177.085 377.087 176.775 376.802 176.648 376.427C175.006 371.607 170.353 357.944 168.59 352.766C168.453 352.363 168.547 351.916 168.835 351.602C169.124 351.288 169.561 351.157 169.975 351.26C175.528 352.641 190.687 356.414 196.24 357.796C196.654 357.899 196.978 358.22 197.086 358.632C197.194 359.045 197.067 359.484 196.757 359.776C192.773 363.523 182.259 373.412 178.549 376.901Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M183.475 381.941C183.095 381.827 182.8 381.527 182.69 381.147C182.581 380.766 182.673 380.355 182.934 380.057C186.287 376.226 195.794 365.365 199.396 361.249C199.677 360.928 200.111 360.786 200.527 360.879C200.943 360.972 201.276 361.285 201.393 361.695C202.973 367.194 207.285 382.209 208.865 387.709C208.983 388.119 208.867 388.56 208.564 388.86C208.26 389.159 207.817 389.269 207.409 389.146C202.171 387.57 188.351 383.409 183.475 381.941Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M171.295 377.161C171.386 377.547 171.275 377.952 171 378.237C170.724 378.522 170.323 378.648 169.935 378.571C164.939 377.583 150.78 374.781 145.415 373.719C144.996 373.636 144.657 373.331 144.529 372.924C144.401 372.517 144.506 372.073 144.802 371.766C148.775 367.648 159.622 356.406 163.595 352.288C163.891 351.981 164.331 351.861 164.743 351.974C165.154 352.087 165.471 352.415 165.568 352.831C166.822 358.155 170.129 372.204 171.295 377.161Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M180.715 388.973C180.624 388.587 180.736 388.182 181.011 387.896C181.286 387.611 181.687 387.485 182.076 387.562C187.071 388.551 201.23 391.353 206.595 392.415C207.014 392.498 207.354 392.802 207.481 393.209C207.609 393.616 207.504 394.061 207.208 394.368C203.235 398.485 192.388 409.728 188.415 413.845C188.119 414.152 187.679 414.273 187.268 414.16C186.856 414.047 186.54 413.718 186.442 413.303C185.189 407.979 181.882 393.929 180.715 388.973Z"
        fill="#C72828"
      />
      <path
        d="M178.409 373.406C176.979 373.051 176.283 370.904 176.854 368.612C177.424 366.32 179.045 364.75 180.475 365.106C181.904 365.461 182.6 367.608 182.03 369.9C181.459 372.192 179.838 373.762 178.409 373.406Z"
        fill="#F3C553"
      />
      <path
        d="M171.534 401.028C170.105 400.672 169.409 398.526 169.979 396.233C170.55 393.941 172.171 392.371 173.6 392.727C175.03 393.083 175.726 395.229 175.156 397.522C174.585 399.814 172.964 401.384 171.534 401.028Z"
        fill="#F3C553"
      />
      <path
        d="M186.912 380.005C186.506 378.589 188.017 376.913 190.287 376.261C192.557 375.609 194.727 376.228 195.134 377.644C195.54 379.06 194.03 380.736 191.759 381.388C189.489 382.04 187.319 381.421 186.912 380.005Z"
        fill="#F3C553"
      />
      <path
        d="M157.418 388.464C157.011 387.049 158.522 385.372 160.792 384.72C163.062 384.068 165.232 384.687 165.639 386.102C166.046 387.518 164.535 389.194 162.265 389.847C159.995 390.499 157.824 389.88 157.418 388.464Z"
        fill="#F3C553"
      />
      <path
        d="M162.153 369.436C163.176 368.376 165.383 368.847 167.083 370.487C168.783 372.127 169.332 374.316 168.309 375.376C167.286 376.436 165.079 375.965 163.379 374.325C161.679 372.685 161.131 370.496 162.153 369.436Z"
        fill="#F3C553"
      />
      <path
        d="M184.243 390.732C185.266 389.672 187.473 390.142 189.173 391.782C190.873 393.422 191.422 395.611 190.399 396.671C189.377 397.731 187.169 397.261 185.469 395.621C183.77 393.981 183.22 391.792 184.243 390.732Z"
        fill="#F3C553"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M162.177 131.54L168.13 150.073C168.13 150.073 170.447 157.625 177.334 154.909C182.565 152.846 182.99 148.57 181.76 145.047C180.393 141.127 175.537 127.054 175.537 127.054C175.537 127.054 185.189 131.1 188.777 131.12C192.063 131.138 195.943 128.881 196.774 126.392C198.969 119.827 186.535 111.153 173.567 110.686C160.6 110.22 148.086 118.027 144.238 128.855C139.955 140.908 144.532 145.924 149.953 144.481C155.374 143.038 161.211 133.608 162.177 131.54Z"
        fill="#E2BC9B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M144.881 143.57C144.95 141.397 145.466 138.772 146.563 135.685C150.411 124.857 162.925 117.049 175.892 117.516C184.459 117.824 192.793 121.714 196.83 126.212C196.813 126.272 196.794 126.332 196.774 126.392C195.943 128.881 192.063 131.138 188.777 131.12C185.189 131.1 175.537 127.054 175.537 127.054C175.537 127.054 176.616 130.182 177.901 133.899L177.862 133.883C177.862 133.883 180.162 140.549 182.042 145.984C182.879 149.299 182.109 153.026 177.334 154.909C172.298 156.895 169.705 153.391 168.676 151.363L164.502 138.369C164.479 138.419 164.452 138.474 164.423 138.532L162.177 131.54C161.211 133.608 155.374 143.038 149.953 144.481C148.04 144.99 146.232 144.695 144.881 143.57Z"
        fill="#ECCFB5"
      />
      <path
        d="M162.177 131.54L168.13 150.073C168.13 150.073 170.447 157.625 177.334 154.909C182.565 152.846 182.99 148.57 181.76 145.047C180.393 141.127 175.537 127.054 175.537 127.054C175.537 127.054 185.189 131.1 188.777 131.12C192.063 131.138 195.943 128.881 196.774 126.392C198.969 119.827 186.535 111.153 173.567 110.686C160.6 110.22 148.086 118.027 144.238 128.855C139.955 140.908 144.532 145.924 149.953 144.481C155.374 143.038 161.211 133.608 162.177 131.54Z"
        stroke="#9A7451"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M358.81 168.724L346.657 185.493C346.657 185.493 341.637 192.234 348.294 196.427C353.349 199.612 357.282 197.284 359.531 194.012C362.033 190.372 371.049 177.351 371.049 177.351C371.049 177.351 373.644 188.17 375.881 191.258C377.93 194.086 382.303 195.99 384.96 195.137C391.967 192.885 391.594 176.766 383.847 165.358C376.1 153.95 361.543 148.139 349.841 151.658C336.815 155.575 335.389 162.655 340.033 166.393C344.676 170.131 356.429 169.198 358.81 168.724Z"
        fill="#E2BC9B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M337.627 161.473C339.534 160.164 342.109 158.953 345.445 157.95C357.148 154.431 371.705 160.242 379.452 171.65C384.57 179.187 386.47 188.78 385.149 195.072C385.087 195.095 385.024 195.117 384.96 195.137C382.303 195.99 377.93 194.086 375.881 191.258C373.644 188.17 371.049 177.351 371.049 177.351C371.049 177.351 369.045 180.245 366.664 183.686L366.654 183.643C366.654 183.643 362.383 189.811 358.904 194.844C356.587 197.648 352.908 199.334 348.294 196.427C343.427 193.361 344.802 188.934 345.893 186.774L354.414 175.016C354.356 175.027 354.293 175.039 354.225 175.051L358.81 168.724C356.429 169.198 344.676 170.131 340.033 166.393C338.394 165.074 337.511 163.338 337.627 161.473Z"
        fill="#ECCFB5"
      />
      <path
        d="M358.81 168.724L346.657 185.493C346.657 185.493 341.637 192.234 348.294 196.427C353.349 199.612 357.282 197.284 359.531 194.012C362.033 190.372 371.049 177.351 371.049 177.351C371.049 177.351 373.644 188.17 375.881 191.258C377.93 194.086 382.303 195.99 384.96 195.137C391.967 192.885 391.594 176.766 383.847 165.358C376.1 153.95 361.543 148.139 349.841 151.658C336.815 155.575 335.389 162.655 340.033 166.393C344.676 170.131 356.429 169.198 358.81 168.724Z"
        stroke="#9A7451"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M170.405 247.004L182.652 262.587C182.652 262.587 187.539 268.976 193.144 263.949C197.401 260.132 196.29 255.902 193.869 252.974C191.175 249.716 181.556 238.002 181.556 238.002C181.556 238.002 192.194 238.452 195.623 237.203C198.763 236.059 201.663 232.534 201.574 229.865C201.339 222.824 186.407 218.94 173.875 223.078C161.343 227.216 152.177 239.089 152.346 250.782C152.534 263.798 158.677 266.967 163.335 263.675C167.993 260.382 170.217 249.32 170.405 247.004Z"
        fill="#E2BC9B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M158.175 264.597C157.471 262.499 157.032 259.811 156.984 256.478C156.815 244.785 165.981 232.911 178.513 228.774C186.792 226.04 196.12 226.808 201.564 229.674C201.569 229.737 201.572 229.801 201.574 229.865C201.663 232.534 198.763 236.059 195.623 237.203C192.194 238.452 181.556 238.002 181.556 238.002C181.556 238.002 183.694 240.606 186.237 243.7L186.194 243.698C186.194 243.698 190.75 249.247 194.47 253.768C196.443 256.636 197.03 260.465 193.144 263.949C189.044 267.626 185.329 265.195 183.629 263.624L175.043 252.7C175.038 252.756 175.033 252.817 175.026 252.883L170.405 247.004C170.217 249.32 167.993 260.382 163.335 263.675C161.691 264.837 159.862 265.194 158.175 264.597Z"
        fill="#ECCFB5"
      />
      <path
        d="M170.405 247.004L182.652 262.587C182.652 262.587 187.539 268.976 193.144 263.949C197.401 260.132 196.29 255.902 193.869 252.974C191.175 249.716 181.556 238.002 181.556 238.002C181.556 238.002 192.194 238.452 195.623 237.203C198.763 236.059 201.663 232.534 201.574 229.865C201.339 222.824 186.407 218.94 173.875 223.078C161.343 227.216 152.177 239.089 152.346 250.782C152.534 263.798 158.677 266.967 163.335 263.675C167.993 260.382 170.217 249.32 170.405 247.004Z"
        stroke="#9A7451"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M95.9959 362.242L111.886 355.716C111.886 355.716 118.368 353.152 115.499 347.282C113.32 342.824 109.529 342.748 106.516 344.075C103.162 345.551 91.1199 350.802 91.1199 350.802C91.1199 350.802 94.0079 342.03 93.7749 338.872C93.5619 335.98 91.3069 332.724 89.0599 332.166C83.1319 330.692 76.3659 342.234 76.8589 353.674C77.3509 365.114 85.0909 375.58 94.8839 378.21C105.785 381.139 109.879 376.764 108.232 372.095C106.585 367.427 97.8829 362.948 95.9959 362.242Z"
        fill="#E2BC9B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M88.8979 332.129C88.9519 332.14 89.0059 332.153 89.0599 332.166C91.3069 332.724 93.5619 335.98 93.7749 338.872C94.0079 342.03 91.1199 350.802 91.1199 350.802C91.1199 350.802 93.7969 349.635 96.9769 348.246L96.9659 348.281C96.9659 348.281 102.666 345.796 107.314 343.764C110.173 342.794 113.509 343.21 115.499 347.282C117.596 351.572 114.698 354.096 112.986 355.144L101.842 359.722C101.888 359.739 101.937 359.758 101.991 359.78L95.9959 362.242C97.8829 362.948 106.585 367.427 108.232 372.095C108.813 373.743 108.679 375.354 107.784 376.62C105.867 376.711 103.522 376.44 100.73 375.69C90.9369 373.059 83.1969 362.594 82.7049 351.154C82.3789 343.596 85.2219 335.994 88.8979 332.129Z"
        fill="#ECCFB5"
      />
      <path
        d="M95.9959 362.242L111.886 355.716C111.886 355.716 118.368 353.152 115.499 347.282C113.32 342.824 109.529 342.748 106.516 344.075C103.162 345.551 91.1199 350.802 91.1199 350.802C91.1199 350.802 94.0079 342.03 93.7749 338.872C93.5619 335.98 91.3069 332.724 89.0599 332.166C83.1319 330.692 76.3659 342.234 76.8589 353.674C77.3509 365.114 85.0909 375.58 94.8839 378.21C105.785 381.139 109.879 376.764 108.232 372.095C106.585 367.427 97.8829 362.948 95.9959 362.242Z"
        stroke="#9A7451"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M432.62 424.087L413.46 408.288C413.46 408.288 405.759 401.788 400.91 408.574C397.228 413.727 399.875 418.408 403.613 421.355C407.774 424.635 422.653 436.438 422.653 436.438C422.653 436.438 410.229 437.801 406.675 439.848C403.42 441.722 401.209 446.299 402.173 449.36C404.714 457.435 423.201 459.272 436.331 452.285C449.461 445.298 456.212 429.988 452.246 416.537C447.832 401.565 439.72 398.998 435.405 403.618C431.091 408.238 432.091 421.385 432.62 424.087Z"
        fill="#E2BC9B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M425.391 418.126L432.62 424.087C432.091 421.385 431.091 408.238 435.405 403.618C436.928 401.987 438.924 401.252 441.063 401.642C442.553 403.936 443.926 406.957 445.056 410.791C449.022 424.242 442.271 439.552 429.141 446.539C420.467 451.155 409.454 451.92 402.246 449.579C402.22 449.506 402.196 449.433 402.173 449.36C401.209 446.299 403.42 441.722 406.675 439.848C410.229 437.801 422.653 436.438 422.653 436.438C422.653 436.438 419.346 433.814 415.414 430.698L415.463 430.692C415.463 430.692 408.416 425.102 402.666 420.547C399.462 417.59 397.548 413.279 400.91 408.574C404.456 403.611 409.528 405.754 411.997 407.264L425.43 418.341C425.417 418.275 425.404 418.204 425.391 418.126Z"
        fill="#ECCFB5"
      />
      <path
        d="M432.62 424.087L413.46 408.288C413.46 408.288 405.759 401.788 400.91 408.574C397.228 413.727 399.875 418.408 403.613 421.355C407.774 424.635 422.653 436.438 422.653 436.438C422.653 436.438 410.229 437.801 406.675 439.848C403.42 441.722 401.209 446.299 402.173 449.36C404.714 457.435 423.201 459.272 436.331 452.285C449.461 445.298 456.212 429.988 452.246 416.537C447.832 401.565 439.72 398.998 435.405 403.618C431.091 408.238 432.091 421.385 432.62 424.087Z"
        stroke="#9A7451"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M365.521 236.851C358.686 236.851 353.146 230.065 353.146 221.694C353.146 213.323 358.686 206.537 365.521 206.537C372.355 206.537 377.896 213.323 377.896 221.694C377.896 230.065 372.355 236.851 365.521 236.851Z"
        fill="#6A895B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M368.94 207.124C374.11 208.942 377.897 214.78 377.897 221.694C377.897 229.6 372.943 236.101 366.633 236.791C362.401 234.166 359.494 228.753 359.494 222.508C359.494 215.183 363.494 209.003 368.94 207.124Z"
        fill="#5C784D"
      />
      <path
        d="M365.521 236.851C358.686 236.851 353.146 230.065 353.146 221.694C353.146 213.323 358.686 206.537 365.521 206.537C372.355 206.537 377.896 213.323 377.896 221.694C377.896 230.065 372.355 236.851 365.521 236.851Z"
        stroke="#374536"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M410.246 264.415C415.77 257.654 426.961 257.657 435.242 264.422C443.522 271.187 445.757 282.153 440.233 288.914C434.709 295.675 423.518 295.672 415.238 288.907C406.957 282.142 404.722 271.177 410.246 264.415Z"
        fill="#6A895B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M409.409 265.563C415.425 263.499 423.129 264.997 429.306 270.044C436.552 275.964 439.433 284.915 436.891 291.821C430.914 295.466 422.078 294.494 415.239 288.907C407.418 282.517 404.99 272.363 409.409 265.563Z"
        fill="#5C784D"
      />
      <path
        d="M410.246 264.415C415.77 257.654 426.961 257.657 435.242 264.422C443.522 271.187 445.757 282.153 440.233 288.914C434.709 295.675 423.518 295.672 415.238 288.907C406.957 282.142 404.722 271.177 410.246 264.415Z"
        stroke="#374536"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M308.041 476.524C313.564 469.763 324.755 469.766 333.036 476.531C341.316 483.296 343.551 494.261 338.028 501.022C332.504 507.783 321.313 507.78 313.033 501.015C304.752 494.25 302.517 483.285 308.041 476.524Z"
        fill="#6A895B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M307.202 477.672C313.219 475.608 320.922 477.107 327.099 482.154C334.345 488.073 337.226 497.025 334.684 503.931C328.707 507.575 319.871 506.603 313.032 501.016C305.211 494.626 302.783 484.472 307.202 477.672Z"
        fill="#5C784D"
      />
      <path
        d="M308.041 476.524C313.564 469.763 324.755 469.766 333.036 476.531C341.316 483.296 343.551 494.261 338.028 501.022C332.504 507.783 321.313 507.78 313.033 501.015C304.752 494.25 302.517 483.285 308.041 476.524Z"
        stroke="#374536"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M237.608 186.269C245.454 182.438 255.617 187.123 260.308 196.732C264.999 206.341 262.441 217.235 254.595 221.066C246.749 224.896 236.586 220.211 231.895 210.602C227.204 200.993 229.762 190.099 237.608 186.269Z"
        fill="#6A895B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M236.367 186.962C242.695 187.604 249.065 192.188 252.564 199.356C256.669 207.765 255.541 217.099 250.343 222.309C243.39 223.119 235.77 218.539 231.896 210.603C227.465 201.527 229.509 191.288 236.367 186.962Z"
        fill="#5C784D"
      />
      <path
        d="M237.608 186.269C245.454 182.438 255.617 187.123 260.308 196.732C264.999 206.341 262.441 217.235 254.595 221.066C246.749 224.896 236.586 220.211 231.895 210.602C227.204 200.993 229.762 190.099 237.608 186.269Z"
        stroke="#374536"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M122.332 386.965C129.237 392.309 129.528 403.496 122.983 411.952C116.438 420.407 105.535 422.93 98.6307 417.585C91.7264 412.241 91.4351 401.054 97.9801 392.598C104.525 384.143 115.428 381.62 122.332 386.965Z"
        fill="#6A895B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M95.6369 414.321C91.8359 408.442 92.5749 399.582 97.9809 392.599C104.163 384.612 114.25 381.92 121.163 386.157C123.385 392.118 122.09 399.858 117.207 406.166C111.48 413.565 102.608 416.68 95.6369 414.321Z"
        fill="#5C784D"
      />
      <path
        d="M122.332 386.965C129.237 392.309 129.528 403.496 122.983 411.952C116.438 420.407 105.535 422.93 98.6307 417.585C91.7264 412.241 91.4351 401.054 97.9801 392.598C104.525 384.143 115.428 381.62 122.332 386.965Z"
        stroke="#374536"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M488.905 193.696C491.052 189.072 489.044 183.583 484.42 181.436C483.223 180.879 481.99 180.307 480.793 179.751C476.169 177.604 470.68 179.611 468.532 184.235C465.778 190.167 462.569 197.076 462.569 197.076L482.942 206.537C482.942 206.537 486.15 199.628 488.905 193.696Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M469.664 200.371C469.68 200.336 472.879 193.447 475.627 187.53C477.498 183.5 481.907 181.458 486.07 182.421C489.491 184.98 490.776 189.666 488.905 193.696C486.15 199.628 482.942 206.537 482.942 206.537L469.664 200.371Z"
        fill="#EFC415"
      />
      <path
        d="M488.905 193.696C491.052 189.072 489.044 183.583 484.42 181.436C483.223 180.879 481.99 180.307 480.793 179.751C476.169 177.604 470.68 179.611 468.532 184.235C465.778 190.167 462.569 197.076 462.569 197.076L482.942 206.537C482.942 206.537 486.15 199.628 488.905 193.696Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M216.181 183.89C216.181 178.792 212.048 174.659 206.95 174.659H202.95C197.852 174.659 193.719 178.792 193.719 183.89V198.048H216.181V183.89Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M208.861 174.857C213.042 175.738 216.181 179.447 216.181 183.89V198.048H201.542V183.89C201.542 179.447 204.681 175.738 208.861 174.857Z"
        fill="#EFC415"
      />
      <path
        d="M216.181 183.89C216.181 178.792 212.048 174.659 206.95 174.659H202.95C197.852 174.659 193.719 178.792 193.719 183.89V198.048H216.181V183.89Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M176.701 305.501C171.996 303.538 166.591 305.762 164.628 310.468C164.12 311.686 163.597 312.94 163.089 314.159C161.127 318.865 163.35 324.27 168.056 326.232C174.093 328.749 181.123 331.681 181.123 331.681L189.769 310.95C189.769 310.95 182.738 308.018 176.701 305.501Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M165.546 308.781C167.968 305.26 172.6 303.79 176.701 305.501L189.768 310.95L185.772 320.533L184.134 324.462C184.087 324.442 177.083 321.522 171.067 319.012C166.966 317.302 164.75 312.978 165.546 308.781Z"
        fill="#EFC415"
      />
      <path
        d="M176.701 305.501C171.996 303.538 166.591 305.762 164.628 310.468C164.12 311.686 163.597 312.94 163.089 314.159C161.127 318.865 163.35 324.27 168.056 326.232C174.093 328.749 181.123 331.681 181.123 331.681L189.769 310.95C189.769 310.95 182.738 308.018 176.701 305.501Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M477.383 344.613C481.978 346.822 487.493 344.889 489.703 340.294C490.275 339.104 490.864 337.88 491.436 336.69C493.646 332.095 491.712 326.579 487.118 324.37C481.223 321.535 474.358 318.234 474.358 318.234L464.624 338.477C464.624 338.477 471.489 341.778 477.383 344.613Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M464.624 338.477L470.968 325.284C470.968 325.284 477.833 328.585 483.728 331.42C487.732 333.345 489.715 337.782 488.696 341.931C486.09 345.317 481.387 346.538 477.383 344.613C471.497 341.782 464.643 338.486 464.624 338.477Z"
        fill="#EFC415"
      />
      <path
        d="M477.383 344.613C481.978 346.822 487.493 344.889 489.703 340.294C490.275 339.104 490.864 337.88 491.436 336.69C493.646 332.095 491.712 326.579 487.118 324.37C481.223 321.535 474.358 318.234 474.358 318.234L464.624 338.477C464.624 338.477 471.489 341.778 477.383 344.613Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M170.822 443.618C167.097 447.098 166.898 452.939 170.379 456.665C171.28 457.63 172.208 458.623 173.109 459.588C176.59 463.313 182.431 463.511 186.156 460.031C190.936 455.566 196.502 450.366 196.502 450.366L181.168 433.952C181.168 433.952 175.602 439.153 170.822 443.618Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M181.168 433.952L191.162 444.65C191.162 444.65 185.596 449.85 180.816 454.315C177.57 457.348 172.717 457.587 169.219 455.133C167.007 451.477 167.576 446.651 170.822 443.618C175.593 439.161 181.147 433.972 181.168 433.952Z"
        fill="#EFC415"
      />
      <path
        d="M170.822 443.618C167.097 447.098 166.898 452.939 170.379 456.665C171.28 457.63 172.208 458.623 173.109 459.588C176.59 463.313 182.431 463.511 186.156 460.031C190.936 455.566 196.502 450.366 196.502 450.366L181.168 433.952C181.168 433.952 175.602 439.153 170.822 443.618Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M340.739 354.135C342.935 358.736 348.445 360.685 353.046 358.489C354.238 357.92 355.464 357.334 356.656 356.765C361.257 354.569 363.206 349.059 361.01 344.458C358.192 338.556 354.91 331.681 354.91 331.681L334.64 341.358C334.64 341.358 337.921 348.232 340.739 354.135Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M347.851 335.051L353.951 347.828C355.865 351.838 354.63 356.538 351.236 359.133C347.083 360.14 342.653 358.145 340.739 354.135C337.921 348.232 334.64 341.358 334.64 341.358L347.851 335.051Z"
        fill="#EFC415"
      />
      <path
        d="M340.739 354.135C342.935 358.736 348.445 360.685 353.046 358.489C354.238 357.92 355.464 357.334 356.656 356.765C361.257 354.569 363.206 349.059 361.01 344.458C358.192 338.556 354.91 331.681 354.91 331.681L334.64 341.358C334.64 341.358 337.921 348.232 340.739 354.135Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M211.483 85.1988C209.792 80.3898 204.522 77.8628 199.712 79.5538C198.466 79.9928 197.185 80.4428 195.939 80.8818C191.13 82.5728 188.602 87.8428 190.294 92.6528C192.464 98.8228 194.992 106.009 194.992 106.009L216.181 98.5548C216.181 98.5548 213.654 91.3688 211.483 85.1988Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M201.581 79.1068C205.818 78.5498 210.009 81.0078 211.483 85.1988C213.654 91.3688 216.181 98.5548 216.181 98.5548L205.949 102.154L202.371 103.413C202.371 103.413 199.843 96.2268 197.673 90.0568C196.198 85.8658 197.929 81.3248 201.581 79.1068Z"
        fill="#EFC415"
      />
      <path
        d="M211.483 85.1988C209.792 80.3898 204.522 77.8628 199.712 79.5538C198.466 79.9928 197.185 80.4428 195.939 80.8818C191.13 82.5728 188.602 87.8428 190.294 92.6528C192.464 98.8228 194.992 106.009 194.992 106.009L216.181 98.5548C216.181 98.5548 213.654 91.3688 211.483 85.1988Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M422.158 141.526C426.05 138.233 426.536 132.409 423.243 128.517C422.39 127.508 421.513 126.471 420.66 125.463C417.367 121.571 411.542 121.085 407.65 124.378C402.657 128.603 396.842 133.523 396.842 133.523L411.35 150.671C411.35 150.671 417.165 145.751 422.158 141.526Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M411.35 150.671L401.894 139.494C401.894 139.494 407.709 134.574 412.703 130.35C416.095 127.48 420.954 127.48 424.326 130.104C426.355 133.864 425.55 138.656 422.158 141.526L411.35 150.671Z"
        fill="#EFC415"
      />
      <path
        d="M422.158 141.526C426.05 138.233 426.536 132.409 423.243 128.517C422.39 127.508 421.513 126.471 420.66 125.463C417.367 121.571 411.542 121.085 407.65 124.378C402.657 128.603 396.842 133.523 396.842 133.523L411.35 150.671C411.35 150.671 417.165 145.751 422.158 141.526Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M315.552 76.5648C315.552 71.4668 311.419 67.3338 306.321 67.3338H302.321C297.223 67.3338 293.09 71.4668 293.09 76.5648V90.7228H315.552V76.5648Z"
        fill="#F5DA70"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M308.232 67.5317C312.413 68.4117 315.552 72.1217 315.552 76.5647V90.7227H300.913V76.5647C300.913 72.1217 304.051 68.4117 308.232 67.5317Z"
        fill="#EFC415"
      />
      <path
        d="M315.552 76.5648C315.552 71.4668 311.419 67.3338 306.321 67.3338H302.321C297.223 67.3338 293.09 71.4668 293.09 76.5648V90.7228H315.552V76.5648Z"
        stroke="#C19607"
        strokeWidth="5.25"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M252.461 309.794C227.769 314.42 204.002 298.154 199.376 273.461C194.75 248.769 211.017 225.002 235.709 220.376C260.401 215.75 284.168 232.017 288.794 256.709C293.42 281.401 277.154 305.168 252.461 309.794Z"
        fill="#F16A65"
        stroke="#C72828"
        strokeWidth="6"
        strokeMiterlimit="10"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M244.589 273.096C244.739 272.729 245.064 272.463 245.454 272.39C245.843 272.317 246.243 272.447 246.515 272.735C250.358 276.8 262.035 289.152 266.167 293.522C266.46 293.832 266.56 294.278 266.428 294.683C266.296 295.089 265.953 295.39 265.534 295.469C259.353 296.627 241.271 300.014 235.089 301.172C234.67 301.251 234.241 301.094 233.972 300.764C233.702 300.433 233.634 299.982 233.795 299.587C236.065 294.017 242.478 278.277 244.589 273.096Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M236.962 268.452C237.355 268.398 237.748 268.547 238.006 268.848C238.264 269.149 238.351 269.56 238.238 269.94C236.639 275.301 231.78 291.588 230.061 297.352C229.939 297.761 229.604 298.07 229.187 298.159C228.77 298.247 228.337 298.101 228.06 297.777C223.966 293.003 211.991 279.037 207.897 274.263C207.62 273.939 207.541 273.49 207.692 273.091C207.844 272.692 208.201 272.408 208.623 272.35C214.582 271.53 231.42 269.214 236.962 268.452Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M243.483 256.548C243.333 256.915 243.007 257.181 242.618 257.254C242.228 257.327 241.829 257.197 241.556 256.909C237.713 252.844 226.037 240.492 221.905 236.122C221.612 235.812 221.512 235.367 221.644 234.961C221.775 234.555 222.118 234.254 222.537 234.176C228.719 233.017 246.801 229.63 252.983 228.472C253.402 228.393 253.83 228.55 254.1 228.88C254.37 229.211 254.438 229.662 254.277 230.057C252.007 235.627 245.594 251.367 243.483 256.548Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M250.74 259.22C250.348 259.274 249.954 259.125 249.697 258.824C249.439 258.523 249.351 258.112 249.464 257.732C251.063 252.371 255.922 236.083 257.641 230.32C257.763 229.911 258.098 229.602 258.515 229.513C258.933 229.424 259.365 229.571 259.643 229.894C263.736 234.669 275.711 248.635 279.805 253.409C280.082 253.733 280.161 254.182 280.01 254.581C279.858 254.98 279.501 255.264 279.079 255.322C273.121 256.142 256.282 258.458 250.74 259.22Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M236.409 260.178C236.652 260.491 236.719 260.906 236.588 261.28C236.456 261.653 236.144 261.935 235.758 262.027C230.316 263.322 213.781 267.259 207.931 268.652C207.516 268.75 207.08 268.615 206.794 268.298C206.509 267.981 206.42 267.533 206.561 267.131C208.649 261.198 214.756 243.845 216.844 237.913C216.986 237.51 217.336 237.217 217.757 237.149C218.178 237.081 218.603 237.248 218.864 237.585C222.553 242.335 232.978 255.76 236.409 260.178Z"
        fill="#C72828"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M251.293 267.494C251.05 267.181 250.983 266.766 251.114 266.392C251.246 266.018 251.558 265.737 251.944 265.645C257.386 264.35 273.921 260.413 279.772 259.02C280.187 258.922 280.622 259.057 280.908 259.374C281.193 259.691 281.283 260.139 281.141 260.541C279.053 266.473 272.946 283.827 270.858 289.759C270.716 290.162 270.366 290.454 269.945 290.523C269.524 290.591 269.1 290.424 268.838 290.087C265.149 285.337 254.724 271.912 251.293 267.494Z"
        fill="#C72828"
      />
      <path
        d="M241.789 252.83C240.161 253.135 238.445 251.265 237.956 248.653C237.467 246.042 238.39 243.677 240.018 243.373C241.646 243.068 243.362 244.938 243.851 247.549C244.34 250.161 243.417 252.525 241.789 252.83Z"
        fill="#F3C553"
      />
      <path
        d="M247.685 284.298C246.057 284.603 244.341 282.733 243.851 280.122C243.362 277.51 244.285 275.146 245.913 274.841C247.541 274.536 249.257 276.406 249.747 279.017C250.236 281.629 249.313 283.993 247.685 284.298Z"
        fill="#F3C553"
      />
      <path
        d="M253.574 255.601C252.496 254.344 253.257 251.922 255.274 250.193C257.291 248.463 259.8 248.08 260.878 249.338C261.956 250.595 261.195 253.016 259.178 254.746C257.161 256.475 254.652 256.858 253.574 255.601Z"
        fill="#F3C553"
      />
      <path
        d="M227.366 278.054C226.288 276.796 227.049 274.375 229.066 272.645C231.083 270.916 233.592 270.533 234.67 271.79C235.748 273.048 234.987 275.469 232.97 277.199C230.953 278.928 228.444 279.311 227.366 278.054Z"
        fill="#F3C553"
      />
      <path
        d="M223.306 256.377C223.856 254.814 226.333 254.263 228.84 255.145C231.346 256.027 232.932 258.009 232.382 259.571C231.832 261.133 229.355 261.685 226.848 260.803C224.342 259.921 222.756 257.939 223.306 256.377Z"
        fill="#F3C553"
      />
      <path
        d="M255.864 267.821C256.414 266.259 258.891 265.707 261.398 266.589C263.904 267.471 265.49 269.453 264.94 271.015C264.39 272.577 261.913 273.129 259.406 272.247C256.9 271.365 255.314 269.383 255.864 267.821Z"
        fill="#F3C553"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M303.524 364.707L298.821 340.323C298.821 340.323 297.026 330.407 288.965 332.548C282.844 334.174 281.902 339.469 282.906 344.122C284.024 349.301 287.973 367.878 287.973 367.878C287.973 367.878 277.528 361.013 273.485 360.327C269.782 359.698 265.162 361.817 263.955 364.791C260.769 372.634 273.848 385.827 288.42 388.806C302.992 391.786 317.949 384.286 323.462 371.391C329.598 357.039 324.98 349.892 318.711 350.704C312.443 351.517 304.838 362.288 303.524 364.707Z"
        fill="#E2BC9B"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M3
Download .txt
gitextract_c85d7kb8/

├── .claude/
│   └── settings.local.json
├── .cursorrules
├── .dockerignore
├── .github/
│   └── workflows/
│       ├── main.yml
│       └── tests.yml
├── .gitignore
├── .prettierrc.js
├── CLAUDE.md
├── Dockerfile
├── LICENSE
├── README.md
├── bin/
│   └── peerjs.js
├── docker-compose.production.yml
├── docker-compose.yml
├── docs/
│   └── file-transfer-protocol.md
├── eslint.config.mjs
├── next-env.d.ts
├── next.config.js
├── package.json
├── playwright.config.ts
├── postcss.config.js
├── public/
│   ├── robots.txt
│   ├── stream.html
│   └── sw.js
├── renovate.json
├── scripts/
│   └── pull-and-run.sh
├── src/
│   ├── app/
│   │   ├── api/
│   │   │   ├── create/
│   │   │   │   └── route.ts
│   │   │   ├── destroy/
│   │   │   │   └── route.ts
│   │   │   ├── ice/
│   │   │   │   └── route.ts
│   │   │   └── renew/
│   │   │       └── route.ts
│   │   ├── download/
│   │   │   └── [...slug]/
│   │   │       └── page.tsx
│   │   ├── layout.tsx
│   │   ├── not-found.tsx
│   │   ├── page.tsx
│   │   └── reported/
│   │       └── page.tsx
│   ├── channel.ts
│   ├── components/
│   │   ├── AddFilesButton.tsx
│   │   ├── CancelButton.tsx
│   │   ├── ConnectionListItem.tsx
│   │   ├── CopyableInput.tsx
│   │   ├── DownloadButton.tsx
│   │   ├── Downloader.tsx
│   │   ├── DropZone.tsx
│   │   ├── ErrorMessage.tsx
│   │   ├── Footer.tsx
│   │   ├── InputLabel.tsx
│   │   ├── Loading.tsx
│   │   ├── ModeToggle.tsx
│   │   ├── PasswordField.tsx
│   │   ├── ProgressBar.tsx
│   │   ├── QueryClientProvider.tsx
│   │   ├── ReportTermsViolationButton.tsx
│   │   ├── ReturnHome.tsx
│   │   ├── Spinner.tsx
│   │   ├── StartButton.tsx
│   │   ├── StopButton.tsx
│   │   ├── SubtitleText.tsx
│   │   ├── TermsAcceptance.tsx
│   │   ├── ThemeProvider.tsx
│   │   ├── TitleText.tsx
│   │   ├── TypeBadge.tsx
│   │   ├── UnlockButton.tsx
│   │   ├── UploadFileList.tsx
│   │   ├── Uploader.tsx
│   │   ├── WebRTCProvider.tsx
│   │   └── Wordmark.tsx
│   ├── config.ts
│   ├── coturn.ts
│   ├── fs.ts
│   ├── hooks/
│   │   ├── useClipboard.ts
│   │   ├── useDownloader.ts
│   │   ├── useRotatingSpinner.ts
│   │   ├── useUploaderChannel.ts
│   │   └── useUploaderConnections.ts
│   ├── log.ts
│   ├── messages.ts
│   ├── redisClient.ts
│   ├── routes.ts
│   ├── slugs.ts
│   ├── styles.css
│   ├── toppings.ts
│   ├── types.ts
│   ├── utils/
│   │   ├── download.ts
│   │   └── pluralize.ts
│   └── zip-stream.ts
├── tests/
│   ├── e2e/
│   │   ├── add-files.test.ts
│   │   ├── basic.test.ts
│   │   ├── file-transfer.test.ts
│   │   └── helpers.ts
│   └── unit/
│       ├── CancelButton.test.tsx
│       ├── ConnectionListItem.test.tsx
│       ├── CopyableInput.test.tsx
│       ├── DownloadButton.test.tsx
│       ├── Downloader.subcomponents.test.tsx
│       ├── DropZone.test.tsx
│       ├── ErrorMessage.test.tsx
│       ├── Footer.test.tsx
│       ├── InputLabel.test.tsx
│       ├── Loading.test.tsx
│       ├── ModeToggle.test.tsx
│       ├── PasswordField.test.tsx
│       ├── ProgressBar.test.tsx
│       ├── QueryClientProvider.test.tsx
│       ├── ReportTermsViolationButton.test.tsx
│       ├── ReturnHome.test.tsx
│       ├── Spinner.test.tsx
│       ├── StartButton.test.tsx
│       ├── StopButton.test.tsx
│       ├── TermsAcceptance.test.tsx
│       ├── ThemeProvider.test.tsx
│       ├── TitleText.test.tsx
│       ├── TypeBadge.test.tsx
│       ├── UnlockButton.test.tsx
│       ├── UploadFileList.test.tsx
│       ├── Uploader.test.tsx
│       ├── WebRTCProvider.test.tsx
│       ├── Wordmark.test.tsx
│       ├── isFinalChunk.test.ts
│       └── useRotatingSpinner.test.ts
├── tsconfig.json
├── vitest.config.ts
└── vitest.setup.ts
Download .txt
SYMBOL INDEX (142 symbols across 59 files)

FILE: public/sw.js
  function createStream (line 55) | function createStream(port) {

FILE: src/app/api/create/route.ts
  function POST (line 4) | async function POST(request: Request): Promise<NextResponse> {

FILE: src/app/api/destroy/route.ts
  function POST (line 4) | async function POST(request: NextRequest): Promise<NextResponse> {

FILE: src/app/api/ice/route.ts
  function POST (line 10) | async function POST(): Promise<NextResponse> {

FILE: src/app/api/renew/route.ts
  function POST (line 4) | async function POST(request: NextRequest): Promise<NextResponse> {

FILE: src/app/download/[...slug]/page.tsx
  function DownloadPage (line 18) | async function DownloadPage({

FILE: src/app/layout.tsx
  function RootLayout (line 29) | function RootLayout({

FILE: src/app/not-found.tsx
  function NotFound (line 12) | async function NotFound(): Promise<JSX.Element> {

FILE: src/app/page.tsx
  function PageWrapper (line 22) | function PageWrapper({ children }: { children: React.ReactNode }): JSX.E...
  function InitialState (line 32) | function InitialState({
  function useUploaderFileListData (line 48) | function useUploaderFileListData(uploadedFiles: UploadedFile[]) {
  function ConfirmUploadState (line 57) | function ConfirmUploadState({
  function UploadingState (line 92) | function UploadingState({
  function UploadPage (line 118) | function UploadPage(): JSX.Element {

FILE: src/app/reported/page.tsx
  function ReportedPage (line 7) | function ReportedPage(): JSX.Element {

FILE: src/channel.ts
  type Channel (line 8) | type Channel = {
  type ChannelRepo (line 22) | interface ChannelRepo {
  function getShortSlugKey (line 29) | function getShortSlugKey(shortSlug: string): string {
  function getLongSlugKey (line 33) | function getLongSlugKey(longSlug: string): string {
  function generateShortSlugUntilUnique (line 37) | async function generateShortSlugUntilUnique(
  function generateLongSlugUntilUnique (line 51) | async function generateLongSlugUntilUnique(
  function serializeChannel (line 65) | function serializeChannel(channel: Channel): string {
  function deserializeChannel (line 69) | function deserializeChannel(str: string, scrubSecret = false): Channel {
  type MemoryStoredChannel (line 78) | type MemoryStoredChannel = {
  class MemoryChannelRepo (line 83) | class MemoryChannelRepo implements ChannelRepo {
    method setChannelTimeout (line 87) | private setChannelTimeout(slug: string, ttl: number) {
    method createChannel (line 103) | async createChannel(
    method fetchChannel (line 136) | async fetchChannel(
    method renewChannel (line 159) | async renewChannel(
    method destroyChannel (line 184) | async destroyChannel(slug: string): Promise<void> {
  class RedisChannelRepo (line 211) | class RedisChannelRepo implements ChannelRepo {
    method constructor (line 214) | constructor() {
    method createChannel (line 218) | async createChannel(
    method fetchChannel (line 243) | async fetchChannel(
    method renewChannel (line 260) | async renewChannel(
    method destroyChannel (line 276) | async destroyChannel(slug: string): Promise<void> {
  function getOrCreateChannelRepo (line 289) | function getOrCreateChannelRepo(): ChannelRepo {

FILE: src/components/AddFilesButton.tsx
  function AddFilesButton (line 4) | function AddFilesButton({

FILE: src/components/CancelButton.tsx
  function CancelButton (line 3) | function CancelButton({

FILE: src/components/ConnectionListItem.tsx
  function ConnectionListItem (line 5) | function ConnectionListItem({

FILE: src/components/CopyableInput.tsx
  function CopyableInput (line 5) | function CopyableInput({

FILE: src/components/DownloadButton.tsx
  function DownloadButton (line 3) | function DownloadButton({

FILE: src/components/Downloader.tsx
  type FileInfo (line 17) | interface FileInfo {
  function ConnectingToUploader (line 23) | function ConnectingToUploader({
  function DownloadComplete (line 88) | function DownloadComplete({
  function DownloadInProgress (line 113) | function DownloadInProgress({
  function ReadyToDownload (line 142) | function ReadyToDownload({
  function PasswordEntry (line 163) | function PasswordEntry({
  function Downloader (line 205) | function Downloader({

FILE: src/components/DropZone.tsx
  function DropZone (line 4) | function DropZone({

FILE: src/components/ErrorMessage.tsx
  function ErrorMessage (line 3) | function ErrorMessage({ message }: { message: string }): JSX.Element {

FILE: src/components/Footer.tsx
  constant DONATE_HREF (line 5) | const DONATE_HREF =
  function FooterLink (line 8) | function FooterLink({
  function Footer (line 27) | function Footer(): JSX.Element {

FILE: src/components/InputLabel.tsx
  function InputLabel (line 3) | function InputLabel({

FILE: src/components/Loading.tsx
  function Loading (line 3) | function Loading({ text }: { text: string }): JSX.Element {

FILE: src/components/ModeToggle.tsx
  function LightModeIcon (line 5) | function LightModeIcon(): JSX.Element {
  function DarkModeIcon (line 24) | function DarkModeIcon(): JSX.Element {
  function ModeToggle (line 43) | function ModeToggle(): JSX.Element {

FILE: src/components/PasswordField.tsx
  function PasswordField (line 4) | function PasswordField({

FILE: src/components/ProgressBar.tsx
  function ProgressBar (line 3) | function ProgressBar({

FILE: src/components/QueryClientProvider.tsx
  function FilePizzaQueryClientProvider (line 7) | function FilePizzaQueryClientProvider({

FILE: src/components/ReportTermsViolationButton.tsx
  function ReportTermsViolationButton (line 9) | function ReportTermsViolationButton({

FILE: src/components/ReturnHome.tsx
  function ReturnHome (line 4) | function ReturnHome(): JSX.Element {

FILE: src/components/Spinner.tsx
  function Pizza (line 6) | function Pizza({ isRotating }: { isRotating?: boolean }): JSX.Element {
  function Arrow (line 1655) | function Arrow({ direction }: { direction: 'up' | 'down' }): JSX.Element {
  function Spinner (line 1684) | function Spinner({

FILE: src/components/StartButton.tsx
  function StartButton (line 3) | function StartButton({

FILE: src/components/StopButton.tsx
  function StopButton (line 3) | function StopButton({

FILE: src/components/SubtitleText.tsx
  function SubtitleText (line 3) | function SubtitleText({

FILE: src/components/TermsAcceptance.tsx
  function TermsAcceptance (line 6) | function TermsAcceptance(): JSX.Element {

FILE: src/components/ThemeProvider.tsx
  type ThemeProviderProps (line 6) | type ThemeProviderProps = Parameters<typeof NextThemesProvider>[0]

FILE: src/components/TitleText.tsx
  function TitleText (line 3) | function TitleText({

FILE: src/components/TypeBadge.tsx
  function getTypeColor (line 3) | function getTypeColor(fileType: string): string {
  function TypeBadge (line 15) | function TypeBadge({ type }: { type: string }): JSX.Element {

FILE: src/components/UnlockButton.tsx
  function UnlockButton (line 3) | function UnlockButton({

FILE: src/components/UploadFileList.tsx
  type UploadedFileLike (line 4) | type UploadedFileLike = {
  function UploadFileList (line 9) | function UploadFileList({

FILE: src/components/Uploader.tsx
  constant QR_CODE_SIZE (line 16) | const QR_CODE_SIZE = 128
  function Uploader (line 18) | function Uploader({

FILE: src/components/WebRTCProvider.tsx
  type WebRTCPeerValue (line 15) | type WebRTCPeerValue = {
  function getOrCreateGlobalPeer (line 32) | async function getOrCreateGlobalPeer(): Promise<Peer> {
  function WebRTCPeerProvider (line 68) | function WebRTCPeerProvider({

FILE: src/components/Wordmark.tsx
  function Wordmark (line 3) | function Wordmark(): JSX.Element {

FILE: src/coturn.ts
  function generateHMACKey (line 4) | function generateHMACKey(
  function setTurnCredentials (line 13) | async function setTurnCredentials(

FILE: src/hooks/useClipboard.ts
  function useClipboard (line 3) | function useClipboard(

FILE: src/hooks/useDownloader.ts
  function useDownloader (line 26) | function useDownloader(uploaderPeerID: string): {

FILE: src/hooks/useRotatingSpinner.ts
  type RotationListener (line 3) | type RotationListener = (isRotating: boolean) => void
  function setRotating (line 8) | function setRotating(rotating: boolean): void {
  function getRotating (line 13) | function getRotating(): boolean {
  function addRotationListener (line 17) | function addRotationListener(listener: RotationListener): void {
  function removeRotationListener (line 21) | function removeRotationListener(listener: RotationListener): void {
  function notifyListeners (line 25) | function notifyListeners(): void {
  function useRotatingSpinner (line 29) | function useRotatingSpinner(): boolean {

FILE: src/hooks/useUploaderChannel.ts
  function generateURL (line 4) | function generateURL(slug: string): string {
  function useUploaderChannel (line 13) | function useUploaderChannel(

FILE: src/hooks/useUploaderConnections.ts
  constant MAX_CHUNK_SIZE (line 19) | const MAX_CHUNK_SIZE = 256 * 1024 // 256 KB
  function isFinalChunk (line 21) | function isFinalChunk(offset: number, fileSize: number): boolean {
  function validateOffset (line 25) | function validateOffset(
  function useUploaderConnections (line 39) | function useUploaderConnections(

FILE: src/messages.ts
  type MessageType (line 3) | enum MessageType {
  type Message (line 100) | type Message = z.infer<typeof Message>
  function decodeMessage (line 102) | function decodeMessage(data: unknown): Message {

FILE: src/redisClient.ts
  function getRedisClient (line 7) | function getRedisClient(): Redis {

FILE: src/routes.ts
  type APIError (line 4) | type APIError = Error & { statusCode?: number }
  type BodyKey (line 6) | type BodyKey = keyof typeof config.bodyKeys
  function throwAPIError (line 8) | function throwAPIError(message: string, statusCode = 500): void {
  function routeHandler (line 14) | function routeHandler<T>(
  function getBodyKey (line 35) | function getBodyKey(req: NextApiRequest, key: BodyKey): string {

FILE: src/slugs.ts
  function generateRandomWords (line 12) | function generateRandomWords(

FILE: src/types.ts
  type UploadedFile (line 3) | type UploadedFile = File & { entryFullPath?: string }
  type UploaderConnectionStatus (line 5) | enum UploaderConnectionStatus {
  type UploaderConnection (line 16) | type UploaderConnection = {

FILE: src/utils/download.ts
  type DownloadFileStream (line 13) | type DownloadFileStream = {
  function streamDownloadSingleFile (line 19) | async function streamDownloadSingleFile(
  function streamDownloadMultipleFiles (line 37) | function streamDownloadMultipleFiles(

FILE: src/utils/pluralize.ts
  function pluralize (line 1) | function pluralize(

FILE: src/zip-stream.ts
  class Crc32 (line 9) | class Crc32 {
    method constructor (line 12) | constructor() {
    method append (line 31) | append(data) {
    method get (line 40) | get() {
  function createZipStream (line 63) | function createZipStream(

FILE: tests/e2e/file-transfer.test.ts
  type TestCase (line 15) | interface TestCase {
  constant CHUNK_SIZE (line 22) | const CHUNK_SIZE = 256 * 1024 // 256 KB

FILE: tests/e2e/helpers.ts
  type TestFile (line 7) | interface TestFile {
  function createTestFile (line 14) | function createTestFile(fileName: string, content: string): TestFile {
  function uploadFile (line 28) | async function uploadFile(
  function addFile (line 69) | async function addFile(
  function startUpload (line 96) | async function startUpload(page: Page): Promise<string> {
  function downloadFile (line 114) | async function downloadFile(
  function verifyFileIntegrity (line 142) | async function verifyFileIntegrity(
  function verifyTransferCompletion (line 156) | async function verifyTransferCompletion(
  function createBrowserContexts (line 165) | async function createBrowserContexts(browser: Browser): Promise<{
  type ProgressMonitor (line 184) | interface ProgressMonitor {
  type ChunkProgressLog (line 190) | interface ChunkProgressLog {
  type PreciseChunkMonitor (line 201) | interface PreciseChunkMonitor {
  function monitorChunkProgress (line 206) | async function monitorChunkProgress(
  function verifyPreciseProgress (line 285) | function verifyPreciseProgress(

FILE: tests/unit/DropZone.test.tsx
  function createFile (line 7) | function createFile(name: string) {

FILE: tests/unit/WebRTCProvider.test.tsx
  method on (line 8) | on(event: string, cb: (id: string) => void) { if (event === 'open') cb('...
  method off (line 8) | off() {}
Condensed preview — 122 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (321K chars).
[
  {
    "path": ".claude/settings.local.json",
    "chars": 143,
    "preview": "{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(pnpm build:*)\",\n      \"Bash(pnpm test:*)\",\n      \"Bash(npm test:*)\"\n    "
  },
  {
    "path": ".cursorrules",
    "chars": 309,
    "preview": "- Use TypeScript.\n- Use function syntax for defining React components. Define the prop types inline.\n- If a value is exp"
  },
  {
    "path": ".dockerignore",
    "chars": 38,
    "preview": ".DS_Store\n.next\nnode_modules\ndist\n.env"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 397,
    "preview": "name: main\non:\n  push:\n    branches:\n      - main\njobs:\n  build:\n    name: Docker build, tag, and push\n    runs-on: ubun"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 524,
    "preview": "name: tests\non: [push]\njobs:\n  tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - us"
  },
  {
    "path": ".gitignore",
    "chars": 59,
    "preview": ".DS_Store\n.next\nnode_modules\ndist\ntsconfig.tsbuildinfo\n.env"
  },
  {
    "path": ".prettierrc.js",
    "chars": 130,
    "preview": "'use strict';\n\nmodule.exports = {\n  semi: false,\n  trailingComma: 'all',\n  singleQuote: true,\n  printWidth: 80,\n  tabWid"
  },
  {
    "path": "CLAUDE.md",
    "chars": 3008,
    "preview": "# FilePizza Development Guide\n\nA peer-to-peer file transfer application built with modern web technologies.\n\n## Prerequi"
  },
  {
    "path": "Dockerfile",
    "chars": 766,
    "preview": "# Stage 1: Dependencies\nFROM node:lts-alpine AS deps\nRUN apk add --no-cache pnpm\nWORKDIR /app\nCOPY package.json pnpm-loc"
  },
  {
    "path": "LICENSE",
    "chars": 5517,
    "preview": "Copyright (c) 2015, Alex Kern\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without m"
  },
  {
    "path": "README.md",
    "chars": 3608,
    "preview": "<a href=\"https://xkcd.com/949/\"><img src=\"http://imgs.xkcd.com/comics/file_transfer.png\" alt=\"XKCD 949\" width=\"30%\" alig"
  },
  {
    "path": "bin/peerjs.js",
    "chars": 261,
    "preview": "#!/usr/bin/env node\nconst express = require('express')\nconst { ExpressPeerServer } = require('peer')\n\nconst app = expres"
  },
  {
    "path": "docker-compose.production.yml",
    "chars": 881,
    "preview": "services:\n  redis:\n    image: redis:latest\n    ports:\n      - 127.0.0.1:6379:6379\n    networks:\n      - filepizza\n    vo"
  },
  {
    "path": "docker-compose.yml",
    "chars": 802,
    "preview": "services:\n  redis:\n    image: redis:latest\n    ports:\n      - 6379:6379\n    networks:\n      - filepizza\n    volumes:\n   "
  },
  {
    "path": "docs/file-transfer-protocol.md",
    "chars": 5408,
    "preview": "# FilePizza File Transfer Protocol\n\nThis document explains the message-based protocol that FilePizza uses to\ntransfer fi"
  },
  {
    "path": "eslint.config.mjs",
    "chars": 893,
    "preview": "// @ts-check\n\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\n\nexport default tseslint.config"
  },
  {
    "path": "next-env.d.ts",
    "chars": 211,
    "preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edite"
  },
  {
    "path": "next.config.js",
    "chars": 355,
    "preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  // Disable strict mode to avoid calling useEffect twice "
  },
  {
    "path": "package.json",
    "chars": 3221,
    "preview": "{\n  \"name\": \"filepizza\",\n  \"version\": \"2.0.0\",\n  \"description\": \"Free peer-to-peer file transfers in your browser.\",\n  \""
  },
  {
    "path": "playwright.config.ts",
    "chars": 385,
    "preview": "import { defineConfig } from '@playwright/test'\n\nexport default defineConfig({\n  testDir: './tests/e2e',\n  workers: 1, /"
  },
  {
    "path": "postcss.config.js",
    "chars": 93,
    "preview": "module.exports = {\n  plugins: {\n    '@tailwindcss/postcss': {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "public/robots.txt",
    "chars": 24,
    "preview": "User-agent: *\nDisallow:\n"
  },
  {
    "path": "public/stream.html",
    "chars": 5616,
    "preview": "<!--\n  https://github.com/jimmywarting/StreamSaver.js/blob/master/mitm.html\n\n\tmitm.html is the lite \"man in the middle\"\n"
  },
  {
    "path": "public/sw.js",
    "chars": 3781,
    "preview": "// https://github.com/jimmywarting/StreamSaver.js/blob/master/sw.js\n\n/* global self ReadableStream Response */\n\nself.add"
  },
  {
    "path": "renovate.json",
    "chars": 114,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:recommended\"\n  ]\n}\n"
  },
  {
    "path": "scripts/pull-and-run.sh",
    "chars": 178,
    "preview": "#!/bin/bash\n\nset -euo pipefail\n\ngit pull origin main\nsudo docker pull kern/filepizza:latest\nsudo docker compose -f docke"
  },
  {
    "path": "src/app/api/create/route.ts",
    "chars": 478,
    "preview": "import { NextResponse } from 'next/server'\nimport { getOrCreateChannelRepo } from '../../../channel'\n\nexport async funct"
  },
  {
    "path": "src/app/api/destroy/route.ts",
    "chars": 755,
    "preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { getOrCreateChannelRepo } from '../../../channel'\n\nexpor"
  },
  {
    "path": "src/app/api/ice/route.ts",
    "chars": 1125,
    "preview": "import { NextResponse } from 'next/server'\nimport crypto from 'crypto'\nimport { setTurnCredentials } from '../../../cotu"
  },
  {
    "path": "src/app/api/renew/route.ts",
    "chars": 554,
    "preview": "import { NextRequest, NextResponse } from 'next/server'\nimport { getOrCreateChannelRepo } from '../../../channel'\n\nexpor"
  },
  {
    "path": "src/app/download/[...slug]/page.tsx",
    "chars": 1324,
    "preview": "import { JSX } from 'react'\nimport { notFound } from 'next/navigation'\nimport { getOrCreateChannelRepo } from '../../../"
  },
  {
    "path": "src/app/layout.tsx",
    "chars": 1434,
    "preview": "import React from 'react'\nimport Footer from '../components/Footer'\nimport '../styles.css'\nimport { ThemeProvider } from"
  },
  {
    "path": "src/app/not-found.tsx",
    "chars": 691,
    "preview": "import { JSX } from 'react'\nimport Spinner from '../components/Spinner'\nimport Wordmark from '../components/Wordmark'\nim"
  },
  {
    "path": "src/app/page.tsx",
    "chars": 4897,
    "preview": "'use client'\n\nimport React, { JSX, useCallback, useState } from 'react'\nimport WebRTCPeerProvider from '../components/We"
  },
  {
    "path": "src/app/reported/page.tsx",
    "chars": 1287,
    "preview": "import { JSX } from 'react'\nimport Spinner from '../../components/Spinner'\nimport Wordmark from '../../components/Wordma"
  },
  {
    "path": "src/channel.ts",
    "chars": 7891,
    "preview": "import 'server-only'\nimport config from './config'\nimport { Redis, getRedisClient } from './redisClient'\nimport { genera"
  },
  {
    "path": "src/components/AddFilesButton.tsx",
    "chars": 1079,
    "preview": "import React, { useRef, useCallback, JSX } from 'react'\nimport { UploadedFile } from '../types'\n\nexport default function"
  },
  {
    "path": "src/components/CancelButton.tsx",
    "chars": 575,
    "preview": "import React, { JSX } from 'react'\n\nexport default function CancelButton({\n  onClick,\n  text = 'Cancel',\n}: {\n  onClick:"
  },
  {
    "path": "src/components/ConnectionListItem.tsx",
    "chars": 2322,
    "preview": "import React, { JSX } from 'react'\nimport { UploaderConnection, UploaderConnectionStatus } from '../types'\nimport Progre"
  },
  {
    "path": "src/components/CopyableInput.tsx",
    "chars": 1097,
    "preview": "import React, { JSX } from 'react'\nimport useClipboard from '../hooks/useClipboard'\nimport InputLabel from './InputLabel"
  },
  {
    "path": "src/components/DownloadButton.tsx",
    "chars": 485,
    "preview": "import React, { JSX } from 'react'\n\nexport default function DownloadButton({\n  onClick,\n}: {\n  onClick?: React.MouseEven"
  },
  {
    "path": "src/components/Downloader.tsx",
    "chars": 7072,
    "preview": "'use client'\n\nimport React, { JSX, useState, useCallback, useEffect } from 'react'\nimport { useDownloader } from '../hoo"
  },
  {
    "path": "src/components/DropZone.tsx",
    "chars": 3288,
    "preview": "import React, { JSX, useState, useCallback, useEffect, useRef } from 'react'\nimport { extractFileList } from '../fs'\n\nex"
  },
  {
    "path": "src/components/ErrorMessage.tsx",
    "chars": 374,
    "preview": "import { JSX } from 'react'\n\nexport function ErrorMessage({ message }: { message: string }): JSX.Element {\n  return (\n  "
  },
  {
    "path": "src/components/Footer.tsx",
    "chars": 2280,
    "preview": "'use client'\n\nimport React, { JSX, useCallback } from 'react'\n\nconst DONATE_HREF =\n  'https://commerce.coinbase.com/chec"
  },
  {
    "path": "src/components/InputLabel.tsx",
    "chars": 1278,
    "preview": "import React, { JSX } from 'react'\n\nexport default function InputLabel({\n  children,\n  hasError = false,\n  tooltip,\n}: {"
  },
  {
    "path": "src/components/Loading.tsx",
    "chars": 268,
    "preview": "import React, { JSX } from 'react'\n\nexport default function Loading({ text }: { text: string }): JSX.Element {\n  return "
  },
  {
    "path": "src/components/ModeToggle.tsx",
    "chars": 1583,
    "preview": "'use client'\n\nimport { useTheme } from 'next-themes'\nimport { JSX } from 'react'\nfunction LightModeIcon(): JSX.Element {"
  },
  {
    "path": "src/components/PasswordField.tsx",
    "chars": 1480,
    "preview": "import React, { JSX, useCallback } from 'react'\nimport InputLabel from './InputLabel'\n\nexport default function PasswordF"
  },
  {
    "path": "src/components/ProgressBar.tsx",
    "chars": 1128,
    "preview": "import React, { JSX } from 'react'\n\nexport default function ProgressBar({\n  value,\n  max,\n}: {\n  value: number\n  max: nu"
  },
  {
    "path": "src/components/QueryClientProvider.tsx",
    "chars": 348,
    "preview": "'use client'\n\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\n\nconst queryClient = new QueryCli"
  },
  {
    "path": "src/components/ReportTermsViolationButton.tsx",
    "chars": 5080,
    "preview": "'use client'\n\nimport { JSX } from 'react'\nimport { useWebRTCPeer } from './WebRTCProvider'\nimport { useCallback, useStat"
  },
  {
    "path": "src/components/ReturnHome.tsx",
    "chars": 357,
    "preview": "import { Link } from 'next-view-transitions'\nimport { JSX } from 'react'\n\nexport default function ReturnHome(): JSX.Elem"
  },
  {
    "path": "src/components/Spinner.tsx",
    "chars": 110894,
    "preview": "'use client'\n\nimport React, { JSX } from 'react'\nimport { useRotatingSpinner } from '../hooks/useRotatingSpinner'\n\nfunct"
  },
  {
    "path": "src/components/StartButton.tsx",
    "chars": 492,
    "preview": "import React from 'react'\n\nexport default function StartButton({\n  onClick,\n}: {\n  onClick: React.MouseEventHandler<HTML"
  },
  {
    "path": "src/components/StopButton.tsx",
    "chars": 737,
    "preview": "import React from 'react'\n\nexport default function StopButton({\n  isDownloading,\n  onClick,\n}: {\n  onClick: React.MouseE"
  },
  {
    "path": "src/components/SubtitleText.tsx",
    "chars": 265,
    "preview": "import React, { JSX } from 'react'\n\nexport default function SubtitleText({\n  children,\n}: {\n  children: React.ReactNode\n"
  },
  {
    "path": "src/components/TermsAcceptance.tsx",
    "chars": 3301,
    "preview": "'use client'\n\nimport { JSX, useState } from 'react'\nimport CancelButton from './CancelButton'\n\nexport default function T"
  },
  {
    "path": "src/components/ThemeProvider.tsx",
    "chars": 266,
    "preview": "'use client'\n\nimport * as React from 'react'\nimport { ThemeProvider as NextThemesProvider } from 'next-themes'\n\nexport t"
  },
  {
    "path": "src/components/TitleText.tsx",
    "chars": 262,
    "preview": "import React, { JSX } from 'react'\n\nexport default function TitleText({\n  children,\n}: {\n  children: React.ReactNode\n}):"
  },
  {
    "path": "src/components/TypeBadge.tsx",
    "chars": 885,
    "preview": "import React, { JSX } from 'react'\n\nfunction getTypeColor(fileType: string): string {\n  if (fileType.startsWith('image/'"
  },
  {
    "path": "src/components/UnlockButton.tsx",
    "chars": 473,
    "preview": "import React, { JSX } from 'react'\n\nexport default function UnlockButton({\n  onClick,\n}: {\n  onClick?: React.MouseEventH"
  },
  {
    "path": "src/components/UploadFileList.tsx",
    "chars": 1277,
    "preview": "import React, { JSX } from 'react'\nimport TypeBadge from './TypeBadge'\n\ntype UploadedFileLike = {\n  fileName?: string\n  "
  },
  {
    "path": "src/components/Uploader.tsx",
    "chars": 2464,
    "preview": "'use client'\n\nimport React, { JSX, useCallback, useEffect } from 'react'\nimport { UploadedFile, UploaderConnectionStatus"
  },
  {
    "path": "src/components/WebRTCProvider.tsx",
    "chars": 2358,
    "preview": "'use client'\n\nimport React, {\n  JSX,\n  useState,\n  useEffect,\n  useContext,\n  useCallback,\n  useMemo,\n} from 'react'\nimp"
  },
  {
    "path": "src/components/Wordmark.tsx",
    "chars": 8661,
    "preview": "import { JSX } from 'react'\n\nexport default function Wordmark(): JSX.Element {\n  return (\n    <svg\n      width=\"972\"\n   "
  },
  {
    "path": "src/config.ts",
    "chars": 452,
    "preview": "import toppings from './toppings'\n\nexport default {\n  redisURL: 'redis://localhost:6379/0',\n  channel: {\n    ttl: 60 * 6"
  },
  {
    "path": "src/coturn.ts",
    "chars": 786,
    "preview": "import crypto from 'crypto'\nimport { getRedisClient } from './redisClient'\n\nfunction generateHMACKey(\n  username: string"
  },
  {
    "path": "src/fs.ts",
    "chars": 2283,
    "preview": "import { UploadedFile } from './types'\n\nconst getAsFile = (entry: any): Promise<File> =>\n  new Promise((resolve, reject)"
  },
  {
    "path": "src/hooks/useClipboard.ts",
    "chars": 1382,
    "preview": "import { useState, useCallback, useEffect } from 'react'\n\nexport default function useClipboard(\n  text: string,\n  delay "
  },
  {
    "path": "src/hooks/useDownloader.ts",
    "chars": 9549,
    "preview": "import { useState, useCallback, useRef, useEffect } from 'react'\nimport { useWebRTCPeer } from '../components/WebRTCProv"
  },
  {
    "path": "src/hooks/useRotatingSpinner.ts",
    "chars": 973,
    "preview": "import { useEffect, useState } from 'react'\n\ntype RotationListener = (isRotating: boolean) => void\n\nlet isRotating = fal"
  },
  {
    "path": "src/hooks/useUploaderChannel.ts",
    "chars": 3739,
    "preview": "import { useQuery, useMutation } from '@tanstack/react-query'\nimport { useEffect } from 'react'\n\nfunction generateURL(sl"
  },
  {
    "path": "src/hooks/useUploaderConnections.ts",
    "chars": 13596,
    "preview": "import { useState, useEffect } from 'react'\nimport Peer, { DataConnection } from 'peerjs'\nimport {\n  UploadedFile,\n  Upl"
  },
  {
    "path": "src/log.ts",
    "chars": 163,
    "preview": "import debug from 'debug'\n\nexport const error = debug('filepizza:error')\n\nexport const info = debug('filepizza:info')\n\ne"
  },
  {
    "path": "src/messages.ts",
    "chars": 2223,
    "preview": "import { z } from 'zod'\n\nexport enum MessageType {\n  RequestInfo = 'RequestInfo',\n  Info = 'Info',\n  Start = 'Start',\n  "
  },
  {
    "path": "src/redisClient.ts",
    "chars": 277,
    "preview": "import Redis from 'ioredis'\n\nexport { Redis }\n\nlet redisClient: Redis | null = null\n\nexport function getRedisClient(): R"
  },
  {
    "path": "src/routes.ts",
    "chars": 1366,
    "preview": "import { NextApiRequest, NextApiResponse } from 'next'\nimport config from './config'\n\nexport type APIError = Error & { s"
  },
  {
    "path": "src/slugs.ts",
    "chars": 1758,
    "preview": "import 'server-only'\nimport crypto from 'crypto'\nimport config from './config'\n\n/**\n * Generates an array of random word"
  },
  {
    "path": "src/styles.css",
    "chars": 267,
    "preview": "@import 'tailwindcss';\n\n@custom-variant dark (&:where(.dark, .dark *));\n\n@theme {\n  --animate-spin-slow: spin 16s linear"
  },
  {
    "path": "src/toppings.ts",
    "chars": 1329,
    "preview": "export default [\n  'alfalfa',\n  'almonds',\n  'anchovies',\n  'artichoke',\n  'avocado',\n  'bacon',\n  'basil',\n  'bayleaves"
  },
  {
    "path": "src/types.ts",
    "chars": 756,
    "preview": "import type { DataConnection } from 'peerjs'\n\nexport type UploadedFile = File & { entryFullPath?: string }\n\nexport enum "
  },
  {
    "path": "src/utils/download.ts",
    "chars": 1641,
    "preview": "import { createZipStream } from '../zip-stream'\n\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nif (t"
  },
  {
    "path": "src/utils/pluralize.ts",
    "chars": 151,
    "preview": "export function pluralize(\n  count: number,\n  singular: string,\n  plural: string,\n): string {\n  return `${count} ${count"
  },
  {
    "path": "src/zip-stream.ts",
    "chars": 7286,
    "preview": "// Based on https://github.com/jimmywarting/StreamSaver.js/blob/master/examples/zip-stream.js\n//\n// Disabling typechecki"
  },
  {
    "path": "tests/e2e/add-files.test.ts",
    "chars": 595,
    "preview": "/// <reference types=\"@playwright/test\" />\nimport { test, expect } from '@playwright/test'\nimport { createTestFile, uplo"
  },
  {
    "path": "tests/e2e/basic.test.ts",
    "chars": 287,
    "preview": "/// <reference types=\"@playwright/test\" />\nimport { test, expect } from '@playwright/test'\n\ntest('home page loads', asyn"
  },
  {
    "path": "tests/e2e/file-transfer.test.ts",
    "chars": 2669,
    "preview": "/// <reference types=\"@playwright/test\" />\nimport { test, expect } from '@playwright/test'\nimport {\n  createTestFile,\n  "
  },
  {
    "path": "tests/e2e/helpers.ts",
    "chars": 8988,
    "preview": "import { Page, Browser, expect } from '@playwright/test'\nimport { createHash } from 'crypto'\nimport { writeFileSync, rea"
  },
  {
    "path": "tests/unit/CancelButton.test.tsx",
    "chars": 510,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/ConnectionListItem.test.tsx",
    "chars": 893,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/CopyableInput.test.tsx",
    "chars": 709,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/DownloadButton.test.tsx",
    "chars": 492,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/Downloader.subcomponents.test.tsx",
    "chars": 2016,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport { vi } from 'vitest'\nvi.mock('next-view-transitions', () => ("
  },
  {
    "path": "tests/unit/DropZone.test.tsx",
    "chars": 685,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/ErrorMessage.test.tsx",
    "chars": 427,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/Footer.test.tsx",
    "chars": 539,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/InputLabel.test.tsx",
    "chars": 542,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/Loading.test.tsx",
    "chars": 397,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/ModeToggle.test.tsx",
    "chars": 575,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/PasswordField.test.tsx",
    "chars": 616,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/ProgressBar.test.tsx",
    "chars": 437,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/QueryClientProvider.test.tsx",
    "chars": 532,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/ReportTermsViolationButton.test.tsx",
    "chars": 941,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/ReturnHome.test.tsx",
    "chars": 532,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nvi.mock(\"next-view-transitions\", () => ({ Link: (p: any) => <a {...p"
  },
  {
    "path": "tests/unit/Spinner.test.tsx",
    "chars": 634,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/StartButton.test.tsx",
    "chars": 477,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/StopButton.test.tsx",
    "chars": 676,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/TermsAcceptance.test.tsx",
    "chars": 482,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/ThemeProvider.test.tsx",
    "chars": 611,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nObject.defineProperty(window, \"matchMedia\", { value: () => ({ matche"
  },
  {
    "path": "tests/unit/TitleText.test.tsx",
    "chars": 413,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/TypeBadge.test.tsx",
    "chars": 415,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/UnlockButton.test.tsx",
    "chars": 482,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/UploadFileList.test.tsx",
    "chars": 568,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, fireEvent } from '@testin"
  },
  {
    "path": "tests/unit/Uploader.test.tsx",
    "chars": 1438,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/WebRTCProvider.test.tsx",
    "chars": 824,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render, waitFor } from '@testing-"
  },
  {
    "path": "tests/unit/Wordmark.test.tsx",
    "chars": 408,
    "preview": "/// <reference types=\"@testing-library/jest-dom\" />\nimport React from 'react'\nimport { render } from '@testing-library/r"
  },
  {
    "path": "tests/unit/isFinalChunk.test.ts",
    "chars": 568,
    "preview": "import { describe, it, expect } from 'vitest'\nimport { isFinalChunk, MAX_CHUNK_SIZE } from '../../src/hooks/useUploaderC"
  },
  {
    "path": "tests/unit/useRotatingSpinner.test.ts",
    "chars": 612,
    "preview": "import { describe, it, expect, vi } from 'vitest'\nimport {\n  setRotating,\n  addRotationListener,\n  removeRotationListene"
  },
  {
    "path": "tsconfig.json",
    "chars": 886,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    "
  },
  {
    "path": "vitest.config.ts",
    "chars": 371,
    "preview": "import react from '@vitejs/plugin-react'\nimport { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  p"
  },
  {
    "path": "vitest.setup.ts",
    "chars": 35,
    "preview": "import '@testing-library/jest-dom'\n"
  }
]

About this extraction

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

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

Copied to clipboard!