Compare commits

..

3 Commits

Author SHA1 Message Date
yannickschuchmann
a7b81d5791 Add README documentation and refactor test suite
Replace minimal README with full project docs (setup, usage, architecture).
Remove trivial tests and add meaningful edge case coverage.
2026-03-08 20:39:15 +00:00
yannickschuchmann
b936095286 Add implementation 2026-03-08 18:01:28 +00:00
yannickschuchmann
cc5394c3db Add CLAUDE.md, plan and tasklist 2026-03-08 13:49:28 +00:00
77 changed files with 68492 additions and 7120 deletions

42
.gitignore vendored
View File

@@ -1,41 +1 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. node_modules
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

55
CLAUDE.md Normal file
View File

@@ -0,0 +1,55 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
P2P Poll App — a peer-to-peer polling application for the Evocracy democratic coding research experiment (https://demcode.evocracy.org/).
### Goal
Build a simple P2P polling app where users can add poll options and vote. Data exchange works peer-to-peer between clients (no central server for data), though a signaling/sync server for connection establishment is acceptable.
### Research Context
- **Phase 1 (2 weeks):** Submit individual code proposal
- **Phase 2:** Groups of 3 merge solutions into a working prototype
- **Phase 3:** Representatives iteratively merge until one final solution remains
- Code must be clean, explainable, and easy to merge with others' work
- Final code published open-source under MIT license
- Language: JavaScript/TypeScript
## Tech Stack
- **Runtime:** Bun
- **Framework:** Waku (minimal React framework with RSC support)
- **Styling:** Tailwind CSS v4
- **P2P/Data sync:** Automerge with automerge-repo (CRDT-based)
- **Networking:** `automerge-repo-network-websocket` (WebSocketClientAdapter) + `automerge-repo-network-broadcastchannel` (cross-tab sync)
- **Storage:** `automerge-repo-storage-indexeddb` (client-side persistence)
**Waku documentation:** https://waku.gg/llms.txt
## Commands
```bash
bun install # Install dependencies
bun run dev # Start dev server
bun run build # Production build
bun test # Run tests (Bun's built-in test runner)
```
## Architecture
- Waku pages router: pages live in `src/pages/`, layouts in `_layout.tsx`
- Client components use `'use client'` directive
- Automerge Repo is initialized in a client-side provider component wrapping the app
- The shared CRDT document holds the poll state (title, options, votes)
- Peers sync via a lightweight WebSocket sync server (can use `automerge-repo` sync server or the public `wss://sync.automerge.org`)
- `BroadcastChannelNetworkAdapter` enables cross-tab sync
- `useDocument` hook from `@automerge/automerge-repo-react-hooks` for reactive document access
- Every Waku page/layout must export `getConfig` specifying render mode (`'static'` or `'dynamic'`)
## Key Design Principles
- Keep it simple and merge-friendly for Phase 2 group work
- Minimal file count, clear separation of concerns
- No over-engineering — this is a research experiment, not production software

View File

@@ -1,2 +1,91 @@
# P2P Poll App # P2P Poll App
Remove-Item -Recurse -Force node_modules, .next
A peer-to-peer polling application where users can create polls, add options, and vote — all without a central server. Built for the [Evocracy democratic coding research experiment](https://demcode.evocracy.org/).
## How It Works
Data is synchronized directly between peers using [Automerge](https://automerge.org/) CRDTs (Conflict-free Replicated Data Types). There is no backend database — every client holds a full copy of the poll document and changes merge automatically, even when made offline or concurrently.
**Sync layers:**
- **WebSocket** (`wss://sync.automerge.org`) — cross-device sync via a public relay
- **BroadcastChannel** — instant cross-tab sync within the same browser
- **IndexedDB** — local persistence across page reloads and offline use
## Requirements
- [Bun](https://bun.sh/) (standalone runtime, no Node.js needed)
## Getting Started
```bash
bun install # Install dependencies
bun run dev # Start dev server (http://localhost:3000)
```
## Usage
1. **Create a poll** — Enter a title on the home page and click "Create"
2. **Share it** — Copy the shareable link and send it to others
3. **Vote** — Click an option to vote; click again to unvote
4. **Add options** — Anyone with the link can add new poll options
Each browser gets a stable peer ID (stored in localStorage) so votes are tracked per-device and double-voting is prevented.
## Tech Stack
| Layer | Technology |
|---|---|
| Runtime | [Bun](https://bun.sh/) |
| Framework | [Waku](https://waku.gg/) (React Server Components) |
| Styling | Tailwind CSS v4 |
| P2P sync | Automerge + automerge-repo |
| Storage | IndexedDB (client-side) |
## Project Structure
```
src/
├── pages/ # Waku page router
│ ├── _root.tsx # HTML document shell
│ ├── _layout.tsx # Root layout + Providers
│ ├── index.tsx # Home page (create/join polls)
│ └── poll/[id].tsx # Poll view page
├── components/
│ ├── Providers.tsx # Automerge Repo initialization
│ ├── HomeClient.tsx # Create/join poll UI
│ ├── PollPageClient.tsx # Poll ID validation
│ ├── PollView.tsx # Poll display, voting, options
│ └── ConnectionStatus.tsx # P2P connection indicator
├── lib/
│ ├── types.ts # Poll & PollOption interfaces
│ ├── repo.ts # Automerge Repo singleton
│ ├── poll.ts # Pure poll mutation functions
│ ├── peer.ts # Peer ID management
│ └── __tests__/ # Unit tests
└── styles/
└── global.css # Tailwind CSS
```
## Testing
```bash
bun test
```
Covers poll creation, voting/unvoting, double-vote prevention, option management, and peer ID persistence.
## Architecture Notes
- **Pure business logic** — Poll mutations in `src/lib/poll.ts` are pure functions, used inside Automerge's `changeDoc()` for CRDT-safe updates
- **No server state** — The WebSocket relay only forwards sync messages; it never stores or processes poll data
- **Offline-first** — The app works fully offline; changes sync when connectivity resumes
- **Conflict-free** — Concurrent edits (e.g., two users voting at the same time) merge automatically without conflicts
## Built With
This project was built in collaboration with [Claude Code](https://claude.ai/code), Anthropic's agentic coding tool.
## License
MIT

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,26 +0,0 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -1,25 +0,0 @@
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "p2p polling",
description: "creating a p2p-polling app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html
lang="en"
className={` h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
</html>
);
}

View File

@@ -1,47 +0,0 @@
"use client";
import { useState } from "react";
import usePeerManager from "../hooks/usePeerManager";
import usePollManager from "../hooks/usePollManager";
import PollCreation from "../components/PollCreation";
import PollActive from "../components/PollActive";
import PeersList from "../components/PeersList";
export default function Page() {
const peerManager = usePeerManager();
const pollManager = usePollManager(peerManager);
const [connectId, setConnectId] = useState("");
return (
<div className="p-6 space-y-4">
<h1 className="text-2xl font-bold">P2P Poll App</h1>
{/* Connect */}
<div className="flex gap-2">
<input
className="border p-2"
placeholder="Peer ID"
value={connectId}
onChange={(e) => setConnectId(e.target.value)}
/>
<button
className="bg-blue-500 text-white px-4"
onClick={() => peerManager.connectToPeer(connectId)}
>
Connect
</button>
</div>
<p>Your ID: {peerManager.peerId}</p>
{pollManager.poll ? (
<PollActive pollManager={pollManager} peerId={peerManager.peerId} />
) : (
<PollCreation pollManager={pollManager} />
)}
<PeersList peers={peerManager.peers} />
</div>
);
}

588
bun.lock Normal file
View File

@@ -0,0 +1,588 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "p2p-poll-app",
"dependencies": {
"@automerge/automerge": "^3.2.4",
"@automerge/automerge-repo": "^2.5.3",
"@automerge/automerge-repo-network-broadcastchannel": "^2.5.3",
"@automerge/automerge-repo-network-websocket": "^2.5.3",
"@automerge/automerge-repo-react-hooks": "^2.5.3",
"@automerge/automerge-repo-storage-indexeddb": "^2.5.3",
"@tailwindcss/vite": "^4.2.1",
"react": "~19.2.4",
"react-dom": "~19.2.4",
"react-server-dom-webpack": "~19.2.4",
"tailwindcss": "^4.2.1",
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-wasm": "^3.5.0",
"waku": "1.0.0-alpha.5",
},
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"typescript": "^5.9.3",
},
},
},
"packages": {
"@automerge/automerge": ["@automerge/automerge@3.2.4", "", {}, "sha512-/IAShHSxme5d4ZK0Vs4A0P+tGaR/bSz6KtIJSCIPfwilCxsIqfRHAoNjmsXip6TGouadmbuw3WfLop6cal8pPQ=="],
"@automerge/automerge-repo": ["@automerge/automerge-repo@2.5.3", "", { "dependencies": { "@automerge/automerge": "2.2.8 - 3", "bs58check": "^3.0.1", "cbor-x": "^1.3.0", "debug": "^4.3.4", "eventemitter3": "^5.0.1", "fast-sha256": "^1.3.0", "uuid": "^9.0.0", "xstate": "^5.9.1" } }, "sha512-5ppzsUlzCs6PY+GeFlquaYLnVnYPz/hWzedep37zjb15tqZju1ukPPkBzT7KGEhEAnA99l4vfhfqSVC+1GktJg=="],
"@automerge/automerge-repo-network-broadcastchannel": ["@automerge/automerge-repo-network-broadcastchannel@2.5.3", "", { "dependencies": { "@automerge/automerge-repo": "2.5.3" } }, "sha512-V/JpOsOkPPqe+hJs7Zs8oSKO3dp2/4Qd6D+x3D1rda7OhyOmlJ1HnMLghycnhHDQG8lCP9ex3AH6MYe1ZqgCKw=="],
"@automerge/automerge-repo-network-websocket": ["@automerge/automerge-repo-network-websocket@2.5.3", "", { "dependencies": { "@automerge/automerge-repo": "2.5.3", "cbor-x": "^1.3.0", "debug": "^4.3.4", "eventemitter3": "^5.0.1", "isomorphic-ws": "^5.0.0", "ws": "^8.7.0" } }, "sha512-p6K1YLo34cyGxJ6oMWqeBbWX9rGvEPGyPdX2eq8S/iWNDjYf8lbjKSOhOD8DX87uAMZN/y1XOY/RckwS8lPJbQ=="],
"@automerge/automerge-repo-react-hooks": ["@automerge/automerge-repo-react-hooks@2.5.3", "", { "dependencies": { "@automerge/automerge": "2.2.8 - 3", "@automerge/automerge-repo": "2.5.3", "eventemitter3": "^5.0.1", "react-usestateref": "^1.0.8" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-573jhVX1LMiBf8zV0NaYT2PgsEuqHNz0RnKSzIp49+g+swgb+KtRhpdiF38tbQoDnkFsKpYGR6P+qoAVW8veaw=="],
"@automerge/automerge-repo-storage-indexeddb": ["@automerge/automerge-repo-storage-indexeddb@2.5.3", "", { "dependencies": { "@automerge/automerge-repo": "2.5.3" } }, "sha512-i4tSzY7sC8ZqurHm4Sd9H1lFL2Don9v9yaSoq0FXi4T+4Wg8eus/mtMlUCmS5XwYQL8GX+epbplrlBaBe+fYiw=="],
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@cbor-extract/cbor-extract-darwin-arm64": ["@cbor-extract/cbor-extract-darwin-arm64@2.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w=="],
"@cbor-extract/cbor-extract-darwin-x64": ["@cbor-extract/cbor-extract-darwin-x64@2.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w=="],
"@cbor-extract/cbor-extract-linux-arm": ["@cbor-extract/cbor-extract-linux-arm@2.2.0", "", { "os": "linux", "cpu": "arm" }, "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q=="],
"@cbor-extract/cbor-extract-linux-arm64": ["@cbor-extract/cbor-extract-linux-arm64@2.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ=="],
"@cbor-extract/cbor-extract-linux-x64": ["@cbor-extract/cbor-extract-linux-x64@2.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw=="],
"@cbor-extract/cbor-extract-win32-x64": ["@cbor-extract/cbor-extract-win32-x64@2.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
"@rollup/plugin-virtual": ["@rollup/plugin-virtual@3.0.2", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
"@swc/core": ["@swc/core@1.15.18", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.18", "@swc/core-darwin-x64": "1.15.18", "@swc/core-linux-arm-gnueabihf": "1.15.18", "@swc/core-linux-arm64-gnu": "1.15.18", "@swc/core-linux-arm64-musl": "1.15.18", "@swc/core-linux-x64-gnu": "1.15.18", "@swc/core-linux-x64-musl": "1.15.18", "@swc/core-win32-arm64-msvc": "1.15.18", "@swc/core-win32-ia32-msvc": "1.15.18", "@swc/core-win32-x64-msvc": "1.15.18" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA=="],
"@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ=="],
"@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.15.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg=="],
"@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.15.18", "", { "os": "linux", "cpu": "arm" }, "sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw=="],
"@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.15.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg=="],
"@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.15.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw=="],
"@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.15.18", "", { "os": "linux", "cpu": "x64" }, "sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA=="],
"@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.15.18", "", { "os": "linux", "cpu": "x64" }, "sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g=="],
"@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.15.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ=="],
"@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.15.18", "", { "os": "win32", "cpu": "ia32" }, "sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg=="],
"@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.15.18", "", { "os": "win32", "cpu": "x64" }, "sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg=="],
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
"@swc/types": ["@swc/types@0.1.25", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g=="],
"@swc/wasm": ["@swc/wasm@1.15.18", "", {}, "sha512-zeSORFArxqUwfVMTRHu8AN9k9LlfSn0CKDSzLhJDITpgLoS0xpnocxsgMjQjUcVYDgO47r9zLP49HEjH/iGsFg=="],
"@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="],
"@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA=="],
"@vitejs/plugin-rsc": ["@vitejs/plugin-rsc@0.5.21", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.5", "es-module-lexer": "^2.0.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.21", "periscopic": "^4.0.2", "srvx": "^0.11.7", "strip-literal": "^3.1.0", "turbo-stream": "^3.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-server-dom-webpack": "*", "vite": "*" }, "optionalPeers": ["react-server-dom-webpack"] }, "sha512-uNayLT8IKvWoznvQyfwKuGiEFV28o7lxUDnw/Av36VCuGpDFZnMmvVCwR37gTvnSmnpul9V0tdJqY3tBKEaDqw=="],
"@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="],
"@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="],
"@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="],
"@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="],
"@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="],
"@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="],
"@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="],
"@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="],
"@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="],
"@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="],
"@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="],
"@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="],
"@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="],
"@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="],
"@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="],
"@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="],
"@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="],
"acorn-loose": ["acorn-loose@8.5.2", "", { "dependencies": { "acorn": "^8.15.0" } }, "sha512-PPvV6g8UGMGgjrMu+n/f9E/tCSkNQ2Y97eFvuVdJfG11+xdIeDcLyNdC8SHcrHbRqkfwLASdplyR6B6sKM1U4A=="],
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
"ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="],
"base-x": ["base-x@4.0.1", "", {}, "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
"bs58": ["bs58@5.0.0", "", { "dependencies": { "base-x": "^4.0.0" } }, "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ=="],
"bs58check": ["bs58check@3.0.1", "", { "dependencies": { "@noble/hashes": "^1.2.0", "bs58": "^5.0.0" } }, "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="],
"cbor-extract": ["cbor-extract@2.2.0", "", { "dependencies": { "node-gyp-build-optional-packages": "5.1.1" }, "optionalDependencies": { "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", "@cbor-extract/cbor-extract-linux-arm": "2.2.0", "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", "@cbor-extract/cbor-extract-linux-x64": "2.2.0", "@cbor-extract/cbor-extract-win32-x64": "2.2.0" }, "bin": { "download-cbor-prebuilds": "bin/download-prebuilds.js" } }, "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA=="],
"cbor-x": ["cbor-x@1.6.0", "", { "optionalDependencies": { "cbor-extract": "^2.2.0" } }, "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg=="],
"chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="],
"commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
"es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="],
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="],
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
"jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
"loader-runner": ["loader-runner@4.3.1", "", {}, "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.1.1", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-test": "build-test.js", "node-gyp-build-optional-packages-optional": "optional.js" } }, "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw=="],
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
"periscopic": ["periscopic@4.0.2", "", { "dependencies": { "@types/estree": "*", "is-reference": "^3.0.2", "zimmerframe": "^1.0.0" } }, "sha512-sqpQDUy8vgB7ycLkendSKS6HnVz1Rneoc3Rc+ZBUCe2pbqlVuCC5vF52l0NJ1aiMg/r1qfYF9/myz8CZeI2rjA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
"react-server-dom-webpack": ["react-server-dom-webpack@19.2.4", "", { "dependencies": { "acorn-loose": "^8.3.0", "neo-async": "^2.6.1", "webpack-sources": "^3.2.0" }, "peerDependencies": { "react": "^19.2.4", "react-dom": "^19.2.4", "webpack": "^5.59.0" } }, "sha512-zEhkWv6RhXDctC2N7yEUHg3751nvFg81ydHj8LTTZuukF/IF1gcOKqqAL6Ds+kS5HtDVACYPik0IvzkgYXPhlQ=="],
"react-usestateref": ["react-usestateref@1.0.9", "", { "peerDependencies": { "react": ">16.0.0" } }, "sha512-t8KLsI7oje0HzfzGhxFXzuwbf1z9vhBM1ptHLUIHhYqZDKFuI5tzdhEVxSNzUkYxwF8XdpOErzHlKxvP7sTERw=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"rsc-html-stream": ["rsc-html-stream@0.0.7", "", {}, "sha512-v9+fuY7usTgvXdNl8JmfXCvSsQbq2YMd60kOeeMIqCJFZ69fViuIxztHei7v5mlMMa2h3SqS+v44Gu9i9xANZA=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="],
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
"srvx": ["srvx@0.11.8", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-2n9t0YnAXPJjinytvxccNgs7rOA5gmE7Wowt/8Dy2dx2fDC6sBhfBpbrCvjYKALlVukPS/Uq3QwkolKNa7P/2Q=="],
"strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="],
"terser-webpack-plugin": ["terser-webpack-plugin@5.3.17", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"turbo-stream": ["turbo-stream@3.2.0", "", {}, "sha512-EK+bZ9UVrVh7JLslVFOV0GEMsociOqVOvEMTAd4ixMyffN5YNIEdLZWXUx5PJqDbTxSIBWw04HS9gCY4frYQDQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
"uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vite-plugin-top-level-await": ["vite-plugin-top-level-await@1.6.0", "", { "dependencies": { "@rollup/plugin-virtual": "^3.0.2", "@swc/core": "^1.12.14", "@swc/wasm": "^1.12.14", "uuid": "10.0.0" }, "peerDependencies": { "vite": ">=2.8" } }, "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww=="],
"vite-plugin-wasm": ["vite-plugin-wasm@3.5.0", "", { "peerDependencies": { "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" } }, "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ=="],
"vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="],
"waku": ["waku@1.0.0-alpha.5", "", { "dependencies": { "@hono/node-server": "^1.19.0", "@vitejs/plugin-react": "^5.1.0", "@vitejs/plugin-rsc": "^0.5.19", "dotenv": "^17.3.1", "hono": "^4.10.0", "magic-string": "^0.30.21", "picocolors": "^1.1.1", "rsc-html-stream": "^0.0.7", "vite": "^7.2.0" }, "peerDependencies": { "react": "~19.2.4", "react-dom": "~19.2.4", "react-server-dom-webpack": "~19.2.4" }, "bin": { "waku": "cli.js" } }, "sha512-DCKZD3grtGvuQdAQyIfqz9PkZoppto7Ysjf5yubD0oXcSQFJSlzLTz7APGuLiGf0NH67PBw1DFGxnV3Tf3J3BA=="],
"watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="],
"webpack": ["webpack@5.105.4", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.20.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.17", "watchpack": "^2.5.1", "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw=="],
"webpack-sources": ["webpack-sources@3.3.4", "", {}, "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q=="],
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
"xstate": ["xstate@5.28.0", "", {}, "sha512-Iaqq6ZrUzqeUtA3hC5LQKZfR8ZLzEFTImMHJM3jWEdVvXWdKvvVLXZEiNQWm3SCA9ZbEou/n5rcsna1wb9t28A=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
"@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@vitejs/plugin-rsc/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.5", "", {}, "sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw=="],
"esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"vite-plugin-top-level-await/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
}
}

View File

View File

@@ -1,22 +0,0 @@
"use client";
type NotificationType = "info" | "success" | "error";
interface NotificationProps {
message: string;
type?: NotificationType;
}
export default function Notification({ message, type = "info" }: NotificationProps) {
const colors: Record<NotificationType, string> = {
info: "bg-blue-100 text-blue-700",
success: "bg-green-100 text-green-700",
error: "bg-red-100 text-red-700",
};
return (
<div className={`px-4 py-2 rounded shadow ${colors[type]}`}>
{message}
</div>
);
}

View File

@@ -1,14 +0,0 @@
"use client";
export default function PeersList({ peers }: { peers: string[] }) {
return (
<div>
<h3>Peers</h3>
{peers.length === 0 ? (
<p>No peers</p>
) : (
peers.map((p) => <div key={p}>{p}</div>)
)}
</div>
);
}

View File

@@ -1,22 +0,0 @@
"use client";
import PollOption from "./PollOption";
export default function PollActive({ pollManager, peerId }: any) {
const poll = pollManager.poll;
return (
<div>
<h2 className="text-xl font-bold">{poll.question}</h2>
{poll.options.map((opt: any) => (
<PollOption
key={opt.id}
option={opt}
pollManager={pollManager}
peerId={peerId}
/>
))}
</div>
);
}

View File

@@ -1,40 +0,0 @@
"use client";
import { useState } from "react";
export default function PollCreation({ pollManager }: any) {
const [question, setQuestion] = useState("");
const [options, setOptions] = useState<string[]>(["", ""]);
return (
<div>
<input
className="border p-2 w-full"
placeholder="Question"
value={question}
onChange={(e) => setQuestion(e.target.value)}
/>
{options.map((opt, i) => (
<input
key={i}
className="border p-2 w-full mt-2"
placeholder={`Option ${i + 1}`}
value={opt}
onChange={(e) => {
const newOpts = [...options];
newOpts[i] = e.target.value;
setOptions(newOpts);
}}
/>
))}
<button
className="bg-green-500 text-white px-4 mt-2"
onClick={() => pollManager.createPoll(question, options)}
>
Create Poll
</button>
</div>
);
}

View File

@@ -1,12 +0,0 @@
"use client";
export default function PollOption({ option, pollManager, peerId }: any) {
return (
<div
className="border p-2 mt-2 cursor-pointer"
onClick={() => pollManager.vote(option.id, peerId)}
>
{option.text} - {option.votes} votes
</div>
);
}

210
dist/public/assets/_id_-cxsEKuSj.js vendored Normal file
View File

@@ -0,0 +1,210 @@
import { r as a, j as e, __tla as __tla_0 } from "./index-I8cR0Dsm.js";
import { i as j, __tla as __tla_1 } from "./fullfat_bundler-C8o4MXnP.js";
import { u as v, a as N } from "./index-ChbWTil4.js";
import { h as p, a as w, u as C, v as k } from "./poll-BI_0HvZY.js";
let $;
let __tla = Promise.all([
(() => {
try {
return __tla_0;
} catch {
}
})(),
(() => {
try {
return __tla_1;
} catch {
}
})()
]).then(async () => {
const h = "p2p-poll-peer-id";
function g() {
return crypto.randomUUID();
}
function S() {
if (typeof globalThis.localStorage > "u") return g();
let s = localStorage.getItem(h);
return s || (s = g(), localStorage.setItem(h, s)), s;
}
function D() {
const s = v(), [r, d] = a.useState(false), l = a.useCallback(() => {
d(s.peers.length > 0);
}, [
s
]);
return a.useEffect(() => {
l();
const o = () => l();
return s.networkSubsystem.on("peer", o), s.networkSubsystem.on("peer-disconnected", o), () => {
s.networkSubsystem.off("peer", o), s.networkSubsystem.off("peer-disconnected", o);
};
}, [
s,
l
]), e.jsxs("div", {
className: "flex items-center gap-2 text-xs text-gray-500",
children: [
e.jsx("span", {
className: `inline-block h-2 w-2 rounded-full ${r ? "bg-green-500" : "bg-yellow-500"}`
}),
r ? "Connected" : "Connecting..."
]
});
}
function I({ docUrl: s }) {
const [r, d] = N(s), [l, o] = a.useState(""), [b, u] = a.useState(false), i = a.useMemo(() => S(), []);
if (!r) return e.jsx("div", {
className: "flex items-center justify-center py-12",
children: e.jsx("div", {
className: "text-gray-500",
children: "Loading poll..."
})
});
const c = r.options.reduce((t, n) => t + n.votes.length, 0), x = () => {
const t = l.trim();
t && (d((n) => w(n, t)), o(""));
}, f = (t) => {
p(r, t, i) ? d((n) => C(n, t, i)) : d((n) => k(n, t, i));
}, y = () => {
const t = window.location.href;
navigator.clipboard.writeText(t).then(() => {
u(true), setTimeout(() => u(false), 2e3);
});
};
return e.jsxs("div", {
className: "space-y-6",
children: [
e.jsxs("div", {
className: "flex items-start justify-between",
children: [
e.jsxs("div", {
children: [
e.jsx("h2", {
className: "text-2xl font-bold",
children: r.title
}),
e.jsxs("p", {
className: "mt-1 text-sm text-gray-500",
children: [
c,
" vote",
c !== 1 ? "s" : "",
" total"
]
})
]
}),
e.jsx(D, {})
]
}),
e.jsxs("div", {
className: "space-y-3",
children: [
r.options.map((t) => {
const n = p(r, t.id, i), m = c > 0 ? t.votes.length / c * 100 : 0;
return e.jsxs("div", {
className: "relative overflow-hidden rounded-lg border border-gray-200 bg-white",
children: [
e.jsx("div", {
className: "absolute inset-y-0 left-0 bg-blue-50 transition-all duration-300",
style: {
width: `${m}%`
}
}),
e.jsxs("div", {
className: "relative flex items-center justify-between px-4 py-3",
children: [
e.jsxs("button", {
onClick: () => f(t.id),
className: `flex items-center gap-2 text-left text-sm font-medium ${n ? "text-blue-600" : "text-gray-700 hover:text-blue-600"}`,
children: [
e.jsx("span", {
className: `flex h-5 w-5 items-center justify-center rounded border text-xs ${n ? "border-blue-600 bg-blue-600 text-white" : "border-gray-300"}`,
children: n ? "\u2713" : ""
}),
t.text
]
}),
e.jsxs("span", {
className: "text-sm text-gray-500",
children: [
t.votes.length,
" (",
m.toFixed(0),
"%)"
]
})
]
})
]
}, t.id);
}),
r.options.length === 0 && e.jsx("p", {
className: "py-4 text-center text-sm text-gray-400",
children: "No options yet. Add one below!"
})
]
}),
e.jsxs("div", {
className: "flex gap-2",
children: [
e.jsx("input", {
type: "text",
value: l,
onChange: (t) => o(t.target.value),
onKeyDown: (t) => t.key === "Enter" && x(),
placeholder: "Add an option...",
className: "flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
}),
e.jsx("button", {
onClick: x,
className: "rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700",
children: "Add"
})
]
}),
e.jsx("div", {
className: "border-t border-gray-200 pt-4",
children: e.jsx("button", {
onClick: y,
className: "rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-50",
children: b ? "Copied!" : "Copy shareable link"
})
})
]
});
}
function U({ id: s }) {
const r = `automerge:${s}`;
return j(r) ? e.jsx("div", {
className: "rounded-lg border border-gray-200 bg-white p-6 shadow-sm",
children: e.jsx(I, {
docUrl: r
})
}) : e.jsxs("div", {
className: "rounded-lg border border-red-200 bg-red-50 p-6 text-center",
children: [
e.jsx("h2", {
className: "text-lg font-semibold text-red-800",
children: "Invalid Poll ID"
}),
e.jsx("p", {
className: "mt-2 text-sm text-red-600",
children: "The poll ID in the URL is not valid."
}),
e.jsx("a", {
href: "/",
className: "mt-4 inline-block text-sm text-blue-600 hover:underline",
children: "Go back home"
})
]
});
}
$ = {
default: U
};
});
export {
__tla,
$ as export_4af94835fa0f
};

49
dist/public/assets/_layout-C9jEQBWP.js vendored Normal file
View File

@@ -0,0 +1,49 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/repo-CjvObF6Z.js","assets/fullfat_bundler-C8o4MXnP.js"])))=>i.map(i=>d[i]);
import { r as a, _ as l, j as i, __tla as __tla_0 } from "./index-I8cR0Dsm.js";
import { R as p } from "./index-ChbWTil4.js";
let v;
let __tla = Promise.all([
(() => {
try {
return __tla_0;
} catch {
}
})()
]).then(async () => {
function f({ children: c }) {
const [o, d] = a.useState(null);
return a.useEffect(() => {
let n, e;
return l(async () => {
const { getRepo: t, cleanupRepo: r } = await import("./repo-CjvObF6Z.js").then(async (m) => {
await m.__tla;
return m;
});
return {
getRepo: t,
cleanupRepo: r
};
}, __vite__mapDeps([0,1])).then(({ getRepo: t, cleanupRepo: r }) => {
const s = t();
d(s), n = r, e = () => {
s.networkSubsystem.adapters.forEach((u) => u.disconnect());
}, window.addEventListener("beforeunload", e);
}), () => {
e && window.removeEventListener("beforeunload", e), n == null ? void 0 : n();
};
}, []), o ? i.jsx(p.Provider, {
value: o,
children: c
}) : i.jsx("div", {
className: "flex min-h-screen items-center justify-center text-gray-400",
children: "Loading..."
});
}
v = {
default: f
};
});
export {
__tla,
v as export_125820ecd802
};

File diff suppressed because one or more lines are too long

Binary file not shown.

6
dist/public/assets/client-CsOmnPdF.js vendored Normal file
View File

@@ -0,0 +1,6 @@
import { I as r, E as o, S as e, C as t } from "./index-I8cR0Dsm.js";
const n = { Children: t, Slot: e }, s = { ErrorBoundary: o, INTERNAL_ServerRouter: r };
export {
s as export_6d786e16fc6b,
n as export_847a2b1045ef
};

File diff suppressed because it is too large Load Diff

21
dist/public/assets/index-BQZM9uZj.js vendored Normal file
View File

@@ -0,0 +1,21 @@
import { r as i, j as t } from "./index-I8cR0Dsm.js";
import { u as m } from "./index-ChbWTil4.js";
import { c as x } from "./poll-BI_0HvZY.js";
function p() {
const [o, d] = i.useState(""), [r, c] = i.useState(""), u = m(), s = () => {
if (!o.trim()) return;
const e = u.create();
e.change((l) => {
const a = x(o.trim());
l.title = a.title, l.options = a.options;
}), window.location.href = `/poll/${e.documentId}`;
}, n = () => {
const e = r.trim();
e && (window.location.href = `/poll/${e}`);
};
return t.jsxs("div", { className: "space-y-8", children: [t.jsx("title", { children: "P2P Poll" }), t.jsxs("section", { className: "rounded-lg border border-gray-200 bg-white p-6 shadow-sm", children: [t.jsx("h2", { className: "mb-4 text-lg font-semibold", children: "Create a New Poll" }), t.jsxs("div", { className: "flex gap-2", children: [t.jsx("input", { type: "text", value: o, onChange: (e) => d(e.target.value), onKeyDown: (e) => e.key === "Enter" && s(), placeholder: "Enter poll title...", className: "flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" }), t.jsx("button", { onClick: s, className: "rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700", children: "Create" })] })] }), t.jsxs("section", { className: "rounded-lg border border-gray-200 bg-white p-6 shadow-sm", children: [t.jsx("h2", { className: "mb-4 text-lg font-semibold", children: "Join an Existing Poll" }), t.jsxs("div", { className: "flex gap-2", children: [t.jsx("input", { type: "text", value: r, onChange: (e) => c(e.target.value), onKeyDown: (e) => e.key === "Enter" && n(), placeholder: "Paste poll ID or link...", className: "flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" }), t.jsx("button", { onClick: n, className: "rounded-md bg-gray-600 px-4 py-2 text-sm font-medium text-white hover:bg-gray-700", children: "Join" })] })] })] });
}
const f = { default: p };
export {
f as export_5ce7e027532e
};

2543
dist/public/assets/index-ChbWTil4.js vendored Normal file

File diff suppressed because it is too large Load Diff

10668
dist/public/assets/index-I8cR0Dsm.js vendored Normal file

File diff suppressed because it is too large Load Diff

27
dist/public/assets/poll-BI_0HvZY.js vendored Normal file
View File

@@ -0,0 +1,27 @@
function u(n) {
return { title: n, options: [] };
}
function c(n, t) {
n.options.push({ id: crypto.randomUUID(), text: t, votes: [] });
}
function f(n, t, i) {
const o = n.options.find((s) => s.id === t);
return o ? o.votes.includes(i) : false;
}
function r(n, t, i) {
const o = n.options.find((s) => s.id === t);
o && (o.votes.includes(i) || o.votes.push(i));
}
function d(n, t, i) {
const o = n.options.find((e) => e.id === t);
if (!o) return;
const s = o.votes.indexOf(i);
s !== -1 && o.votes.splice(s, 1);
}
export {
c as a,
u as c,
f as h,
d as u,
r as v
};

1254
dist/public/assets/repo-CjvObF6Z.js vendored Normal file

File diff suppressed because it is too large Load Diff

13
dist/serve-node.js vendored Normal file
View File

@@ -0,0 +1,13 @@
import { INTERNAL_runFetch, unstable_serverEntry } from './server/index.js';
const { serve } = unstable_serverEntry;
const host = process.env.HOST;
const port = process.env.PORT;
serve({
fetch: (req, ...args) => INTERNAL_runFetch(process.env, req, ...args),
...(host ? { hostname: host } : {}),
...(port ? { port: parseInt(port, 10) } : {}),
});

View File

@@ -0,0 +1,54 @@
export default {
"bootstrapScriptContent": "import(\"/assets/index-I8cR0Dsm.js\")",
"clientReferenceDeps": {
"847a2b1045ef": {
"js": [
"/assets/client-CsOmnPdF.js",
"/assets/index-I8cR0Dsm.js"
],
"css": []
},
"6d786e16fc6b": {
"js": [
"/assets/client-CsOmnPdF.js",
"/assets/index-I8cR0Dsm.js"
],
"css": []
},
"5ce7e027532e": {
"js": [
"/assets/index-BQZM9uZj.js",
"/assets/index-I8cR0Dsm.js",
"/assets/index-ChbWTil4.js",
"/assets/poll-BI_0HvZY.js"
],
"css": []
},
"4af94835fa0f": {
"js": [
"/assets/_id_-cxsEKuSj.js",
"/assets/index-I8cR0Dsm.js",
"/assets/fullfat_bundler-C8o4MXnP.js",
"/assets/index-ChbWTil4.js",
"/assets/poll-BI_0HvZY.js"
],
"css": []
},
"125820ecd802": {
"js": [
"/assets/_layout-C9jEQBWP.js",
"/assets/index-I8cR0Dsm.js",
"/assets/index-ChbWTil4.js"
],
"css": []
}
},
"serverResources": {
"src/pages/_layout.tsx": {
"js": [],
"css": [
"/assets/_layout-ChjUcnq2.css"
]
}
}
}

1
dist/server/__waku_build_metadata.js vendored Normal file
View File

@@ -0,0 +1 @@
export const buildMetadata = new Map([["defineRouter:cachedElements","{}"],["defineRouter:path2moduleIds","{}"]]);

25
dist/server/assets/_id_-D3sxE6t8.js vendored Normal file
View File

@@ -0,0 +1,25 @@
import { r as registerClientReference, a as jsxRuntime_reactServerExports } from "./server-entry-inner-DFzcLk7e.js";
import "node:async_hooks";
import "node:path";
import "http";
import "http2";
import "stream";
import "crypto";
import "fs";
import "path";
import "process";
import "../__vite_rsc_assets_manifest.js";
import "../__waku_build_metadata.js";
const PollPageClient = /* @__PURE__ */ registerClientReference(() => {
throw new Error("Unexpectedly client reference export 'default' is called on server");
}, "4af94835fa0f", "default");
async function PollPage({ id }) {
return /* @__PURE__ */ jsxRuntime_reactServerExports.jsx(PollPageClient, { id });
}
const getConfig = async () => {
return { render: "dynamic" };
};
export {
PollPage as default,
getConfig
};

File diff suppressed because one or more lines are too long

58
dist/server/assets/_layout-Cnq791cx.js vendored Normal file
View File

@@ -0,0 +1,58 @@
import assetsManifest from "../__vite_rsc_assets_manifest.js";
import { _ as __vite_rsc_react__, r as registerClientReference, a as jsxRuntime_reactServerExports } from "./server-entry-inner-DFzcLk7e.js";
import "node:async_hooks";
import "node:path";
import "http";
import "http2";
import "stream";
import "crypto";
import "fs";
import "path";
import "process";
import "../__waku_build_metadata.js";
const RemoveDuplicateServerCss = void 0;
const Resources = /* @__PURE__ */ ((React, deps, RemoveDuplicateServerCss2, precedence) => {
return function Resources2() {
return React.createElement(React.Fragment, null, [...deps.css.map((href) => React.createElement("link", {
key: "css:" + href,
rel: "stylesheet",
...{ precedence },
href,
"data-rsc-css-href": href
})), RemoveDuplicateServerCss2]);
};
})(
__vite_rsc_react__,
assetsManifest.serverResources["src/pages/_layout.tsx"],
RemoveDuplicateServerCss,
"vite-rsc/importer-resources"
);
const Providers = /* @__PURE__ */ registerClientReference(() => {
throw new Error("Unexpectedly client reference export 'default' is called on server");
}, "125820ecd802", "default");
const Layout = ({ children }) => /* @__PURE__ */ jsxRuntime_reactServerExports.jsx(Providers, { children: /* @__PURE__ */ jsxRuntime_reactServerExports.jsxs("div", { className: "min-h-screen bg-gray-50 text-gray-900", children: [
/* @__PURE__ */ jsxRuntime_reactServerExports.jsx("header", { className: "border-b border-gray-200 bg-white px-4 py-3", children: /* @__PURE__ */ jsxRuntime_reactServerExports.jsx("h1", { className: "text-xl font-bold", children: /* @__PURE__ */ jsxRuntime_reactServerExports.jsx("a", { href: "/", className: "hover:text-blue-600", children: "P2P Poll" }) }) }),
/* @__PURE__ */ jsxRuntime_reactServerExports.jsx("main", { className: "mx-auto max-w-xl px-4 py-8", children })
] }) });
const $$default = Layout;
let getConfig = async () => {
return { render: "static" };
};
const $$wrap_$$default = /* @__PURE__ */ __vite_rsc_wrap_css__($$default, "Layout");
function __vite_rsc_wrap_css__(value, name) {
if (typeof value !== "function") return value;
function __wrapper(props) {
return __vite_rsc_react__.createElement(
__vite_rsc_react__.Fragment,
null,
__vite_rsc_react__.createElement(Resources),
__vite_rsc_react__.createElement(value, props)
);
}
Object.defineProperty(__wrapper, "name", { value: name });
return __wrapper;
}
export {
$$wrap_$$default as default,
getConfig
};

28
dist/server/assets/_root-_BZAZd6T.js vendored Normal file
View File

@@ -0,0 +1,28 @@
import { a as jsxRuntime_reactServerExports } from "./server-entry-inner-DFzcLk7e.js";
import "node:async_hooks";
import "node:path";
import "http";
import "http2";
import "stream";
import "crypto";
import "fs";
import "path";
import "process";
import "../__vite_rsc_assets_manifest.js";
import "../__waku_build_metadata.js";
function Root({ children }) {
return /* @__PURE__ */ jsxRuntime_reactServerExports.jsxs("html", { lang: "en", children: [
/* @__PURE__ */ jsxRuntime_reactServerExports.jsxs("head", { children: [
/* @__PURE__ */ jsxRuntime_reactServerExports.jsx("meta", { charSet: "utf-8" }),
/* @__PURE__ */ jsxRuntime_reactServerExports.jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" })
] }),
/* @__PURE__ */ jsxRuntime_reactServerExports.jsx("body", { children })
] });
}
const getConfig = () => {
return { render: "dynamic" };
};
export {
Root as default,
getConfig
};

25
dist/server/assets/index-BU_VRdfq.js vendored Normal file
View File

@@ -0,0 +1,25 @@
import { r as registerClientReference, a as jsxRuntime_reactServerExports } from "./server-entry-inner-DFzcLk7e.js";
import "node:async_hooks";
import "node:path";
import "http";
import "http2";
import "stream";
import "crypto";
import "fs";
import "path";
import "process";
import "../__vite_rsc_assets_manifest.js";
import "../__waku_build_metadata.js";
const HomeClient = /* @__PURE__ */ registerClientReference(() => {
throw new Error("Unexpectedly client reference export 'default' is called on server");
}, "5ce7e027532e", "default");
async function HomePage() {
return /* @__PURE__ */ jsxRuntime_reactServerExports.jsx(HomeClient, {});
}
const getConfig = async () => {
return { render: "dynamic" };
};
export {
HomePage as default,
getConfig
};

File diff suppressed because it is too large Load Diff

54
dist/server/build.js vendored Normal file
View File

@@ -0,0 +1,54 @@
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import { I as INTERNAL_setAllEnv, s as serverEntry, j as joinPath, __tla as __tla_0 } from "./assets/server-entry-inner-DFzcLk7e.js";
import "node:async_hooks";
import "node:path";
import "http";
import "http2";
import "stream";
import "crypto";
import "fs";
import "path";
import "process";
import "./__vite_rsc_assets_manifest.js";
import "./__waku_build_metadata.js";
let INTERNAL_runBuild;
let __tla = Promise.all([
(() => {
try {
return __tla_0;
} catch {
}
})()
]).then(async () => {
function resolveModuleId(moduleId, rootDir) {
if (moduleId.startsWith("file://")) {
return moduleId;
}
if (moduleId.startsWith("/")) {
return pathToFileURL(joinPath(rootDir, moduleId.slice(1))).href;
}
const require2 = createRequire(joinPath(rootDir, "DUMMY.js"));
const resolved = require2.resolve(moduleId);
return pathToFileURL(resolved).href;
}
INTERNAL_runBuild = async function({ rootDir, emitFile }) {
INTERNAL_setAllEnv(process.env);
let build = serverEntry.build;
for (const enhancer of serverEntry.buildEnhancers || []) {
const moduleId = resolveModuleId(enhancer, rootDir);
const mod = await import(moduleId).then(async (m) => {
await m.__tla;
return m;
});
build = await mod.default(build);
}
await build({
emitFile
}, serverEntry.buildOptions || {});
};
});
export {
INTERNAL_runBuild,
__tla
};

22
dist/server/index.js vendored Normal file
View File

@@ -0,0 +1,22 @@
import { s as serverEntry, I as INTERNAL_setAllEnv } from "./assets/server-entry-inner-DFzcLk7e.js";
import "node:async_hooks";
import "node:path";
import "http";
import "http2";
import "stream";
import "crypto";
import "fs";
import "path";
import "process";
import "./__vite_rsc_assets_manifest.js";
import "./__waku_build_metadata.js";
async function INTERNAL_runFetch(env, req, ...args) {
INTERNAL_setAllEnv(env);
return serverEntry.fetch(req, ...args);
}
const entry_server = serverEntry.defaultExport;
export {
INTERNAL_runFetch,
entry_server as default,
serverEntry as unstable_serverEntry
};

View File

@@ -0,0 +1,54 @@
export default {
"bootstrapScriptContent": "import(\"/assets/index-I8cR0Dsm.js\")",
"clientReferenceDeps": {
"847a2b1045ef": {
"js": [
"/assets/client-CsOmnPdF.js",
"/assets/index-I8cR0Dsm.js"
],
"css": []
},
"6d786e16fc6b": {
"js": [
"/assets/client-CsOmnPdF.js",
"/assets/index-I8cR0Dsm.js"
],
"css": []
},
"5ce7e027532e": {
"js": [
"/assets/index-BQZM9uZj.js",
"/assets/index-I8cR0Dsm.js",
"/assets/index-ChbWTil4.js",
"/assets/poll-BI_0HvZY.js"
],
"css": []
},
"4af94835fa0f": {
"js": [
"/assets/_id_-cxsEKuSj.js",
"/assets/index-I8cR0Dsm.js",
"/assets/fullfat_bundler-C8o4MXnP.js",
"/assets/index-ChbWTil4.js",
"/assets/poll-BI_0HvZY.js"
],
"css": []
},
"125820ecd802": {
"js": [
"/assets/_layout-C9jEQBWP.js",
"/assets/index-I8cR0Dsm.js",
"/assets/index-ChbWTil4.js"
],
"css": []
}
},
"serverResources": {
"src/pages/_layout.tsx": {
"js": [],
"css": [
"/assets/_layout-ChjUcnq2.css"
]
}
}
}

192
dist/server/ssr/assets/_id_-BAEItp57.js vendored Normal file
View File

@@ -0,0 +1,192 @@
import { r as reactExports, j as jsxRuntimeExports } from "../index.js";
import { i as isValidAutomergeUrl } from "./fullfat_node-75TjwUrn.js";
import { u as useRepo, a as useDocument } from "./index-BSpyO9eA.js";
import { h as hasVoted, a as addOption, u as unvote, v as vote } from "./poll-R5-eIJ_b.js";
import "../__vite_rsc_assets_manifest.js";
import "node:async_hooks";
import "tty";
import "util";
import "os";
import "node:crypto";
import "crypto";
import "module";
import "fs";
const PEER_ID_KEY = "p2p-poll-peer-id";
function generateUUID() {
return crypto.randomUUID();
}
function getPeerId() {
if (typeof globalThis.localStorage === "undefined") {
return generateUUID();
}
let id = localStorage.getItem(PEER_ID_KEY);
if (!id) {
id = generateUUID();
localStorage.setItem(PEER_ID_KEY, id);
}
return id;
}
function ConnectionStatus() {
const repo = useRepo();
const [connected, setConnected] = reactExports.useState(false);
const updateStatus = reactExports.useCallback(() => {
setConnected(repo.peers.length > 0);
}, [repo]);
reactExports.useEffect(() => {
updateStatus();
const onChange = () => updateStatus();
repo.networkSubsystem.on("peer", onChange);
repo.networkSubsystem.on("peer-disconnected", onChange);
return () => {
repo.networkSubsystem.off("peer", onChange);
repo.networkSubsystem.off("peer-disconnected", onChange);
};
}, [repo, updateStatus]);
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-gray-500", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"span",
{
className: `inline-block h-2 w-2 rounded-full ${connected ? "bg-green-500" : "bg-yellow-500"}`
}
),
connected ? "Connected" : "Connecting..."
] });
}
function PollView({ docUrl }) {
const [doc, changeDoc] = useDocument(docUrl);
const [newOption, setNewOption] = reactExports.useState("");
const [copied, setCopied] = reactExports.useState(false);
const peerId = reactExports.useMemo(() => getPeerId(), []);
if (!doc) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-gray-500", children: "Loading poll..." }) });
}
const totalVotes = doc.options.reduce((sum, o) => sum + o.votes.length, 0);
const handleAddOption = () => {
const text = newOption.trim();
if (!text) return;
changeDoc((d) => addOption(d, text));
setNewOption("");
};
const handleVote = (optionId) => {
if (hasVoted(doc, optionId, peerId)) {
changeDoc((d) => unvote(d, optionId, peerId));
} else {
changeDoc((d) => vote(d, optionId, peerId));
}
};
const handleCopy = () => {
const url = window.location.href;
navigator.clipboard.writeText(url).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2e3);
});
};
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-2xl font-bold", children: doc.title }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "mt-1 text-sm text-gray-500", children: [
totalVotes,
" vote",
totalVotes !== 1 ? "s" : "",
" total"
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx(ConnectionStatus, {})
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
doc.options.map((option) => {
const voted = hasVoted(doc, option.id, peerId);
const pct = totalVotes > 0 ? option.votes.length / totalVotes * 100 : 0;
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
"div",
{
className: "relative overflow-hidden rounded-lg border border-gray-200 bg-white",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
className: "absolute inset-y-0 left-0 bg-blue-50 transition-all duration-300",
style: { width: `${pct}%` }
}
),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex items-center justify-between px-4 py-3", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(
"button",
{
onClick: () => handleVote(option.id),
className: `flex items-center gap-2 text-left text-sm font-medium ${voted ? "text-blue-600" : "text-gray-700 hover:text-blue-600"}`,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"span",
{
className: `flex h-5 w-5 items-center justify-center rounded border text-xs ${voted ? "border-blue-600 bg-blue-600 text-white" : "border-gray-300"}`,
children: voted ? "\u2713" : ""
}
),
option.text
]
}
),
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm text-gray-500", children: [
option.votes.length,
" (",
pct.toFixed(0),
"%)"
] })
] })
]
},
option.id
);
}),
doc.options.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "py-4 text-center text-sm text-gray-400", children: "No options yet. Add one below!" })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
type: "text",
value: newOption,
onChange: (e) => setNewOption(e.target.value),
onKeyDown: (e) => e.key === "Enter" && handleAddOption(),
placeholder: "Add an option...",
className: "flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: handleAddOption,
className: "rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700",
children: "Add"
}
)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-gray-200 pt-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: handleCopy,
className: "rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-50",
children: copied ? "Copied!" : "Copy shareable link"
}
) })
] });
}
function PollPageClient({ id }) {
const automergeUrl = `automerge:${id}`;
if (!isValidAutomergeUrl(automergeUrl)) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-red-200 bg-red-50 p-6 text-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-lg font-semibold text-red-800", children: "Invalid Poll ID" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-2 text-sm text-red-600", children: "The poll ID in the URL is not valid." }),
/* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "/", className: "mt-4 inline-block text-sm text-blue-600 hover:underline", children: "Go back home" })
] });
}
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg border border-gray-200 bg-white p-6 shadow-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsx(PollView, { docUrl: automergeUrl }) });
}
const export_4af94835fa0f = {
default: PollPageClient
};
export {
export_4af94835fa0f
};

View File

@@ -0,0 +1,53 @@
import { r as reactExports, j as jsxRuntimeExports, __tla as __tla_0 } from "../index.js";
import { R as RepoContext } from "./index-BSpyO9eA.js";
import "../__vite_rsc_assets_manifest.js";
import "node:async_hooks";
let export_125820ecd802;
let __tla = Promise.all([
(() => {
try {
return __tla_0;
} catch {
}
})()
]).then(async () => {
function Providers({ children }) {
const [repo, setRepo] = reactExports.useState(null);
reactExports.useEffect(() => {
let cleanup;
let handleBeforeUnload;
import("./repo-zy9lifAg.js").then(({ getRepo, cleanupRepo }) => {
const r = getRepo();
setRepo(r);
cleanup = cleanupRepo;
handleBeforeUnload = () => {
r.networkSubsystem.adapters.forEach((adapter) => adapter.disconnect());
};
window.addEventListener("beforeunload", handleBeforeUnload);
});
return () => {
if (handleBeforeUnload) {
window.removeEventListener("beforeunload", handleBeforeUnload);
}
cleanup == null ? void 0 : cleanup();
};
}, []);
if (!repo) {
return jsxRuntimeExports.jsx("div", {
className: "flex min-h-screen items-center justify-center text-gray-400",
children: "Loading..."
});
}
return jsxRuntimeExports.jsx(RepoContext.Provider, {
value: repo,
children
});
}
export_125820ecd802 = {
default: Providers
};
});
export {
__tla,
export_125820ecd802
};

View File

@@ -0,0 +1,104 @@
import { j as jsxRuntimeExports, S as Slot, r as reactExports, C as Children } from "../index.js";
import "../__vite_rsc_assets_manifest.js";
import "node:async_hooks";
const RouterContext = /* @__PURE__ */ reactExports.createContext(null);
const notAvailableInServer = (name) => () => {
throw new Error(`${name} is not in the server`);
};
function renderError(message) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs("html", {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("head", {
children: /* @__PURE__ */ jsxRuntimeExports.jsx("title", {
children: "Unhandled Error"
})
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("body", {
style: {
height: "100vh",
display: "flex",
flexDirection: "column",
placeContent: "center",
placeItems: "center",
fontSize: "16px",
margin: 0
},
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h1", {
children: "Caught an unexpected error"
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", {
children: [
"Error: ",
message
]
})
]
})
]
});
}
class ErrorBoundary extends reactExports.Component {
constructor(props) {
super(props);
this.state = {};
}
static getDerivedStateFromError(error) {
return {
error
};
}
render() {
if ("error" in this.state) {
if (this.state.error instanceof Error) {
return renderError(this.state.error.message);
}
return renderError(String(this.state.error));
}
return this.props.children;
}
}
const getRouteSlotId = (path) => "route:" + path;
const MOCK_ROUTE_CHANGE_LISTENER = {
on: () => notAvailableInServer("routeChange:on"),
off: () => notAvailableInServer("routeChange:off")
};
function INTERNAL_ServerRouter({ route, httpstatus }) {
const routeElement = /* @__PURE__ */ jsxRuntimeExports.jsx(Slot, {
id: getRouteSlotId(route.path)
});
const rootElement = /* @__PURE__ */ jsxRuntimeExports.jsxs(Slot, {
id: "root",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("meta", {
name: "httpstatus",
content: `${httpstatus}`
}),
routeElement
]
});
return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, {
children: /* @__PURE__ */ jsxRuntimeExports.jsx(RouterContext, {
value: {
route,
changeRoute: notAvailableInServer("changeRoute"),
prefetchRoute: notAvailableInServer("prefetchRoute"),
routeChangeEvents: MOCK_ROUTE_CHANGE_LISTENER,
fetchingSlices: /* @__PURE__ */ new Set()
},
children: rootElement
})
});
}
const export_847a2b1045ef = {
Children,
Slot
};
const export_6d786e16fc6b = {
ErrorBoundary,
INTERNAL_ServerRouter
};
export {
export_6d786e16fc6b,
export_847a2b1045ef
};

File diff suppressed because it is too large Load Diff

5592
dist/server/ssr/assets/index-BSpyO9eA.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
import { r as reactExports, j as jsxRuntimeExports } from "../index.js";
import { u as useRepo } from "./index-BSpyO9eA.js";
import { c as createPoll } from "./poll-R5-eIJ_b.js";
import "../__vite_rsc_assets_manifest.js";
import "node:async_hooks";
function HomeClient() {
const [title, setTitle] = reactExports.useState("");
const [joinId, setJoinId] = reactExports.useState("");
const repo = useRepo();
const handleCreate = () => {
if (!title.trim()) return;
const handle = repo.create();
handle.change((doc) => {
const poll = createPoll(title.trim());
doc.title = poll.title;
doc.options = poll.options;
});
window.location.href = `/poll/${handle.documentId}`;
};
const handleJoin = () => {
const id = joinId.trim();
if (!id) return;
window.location.href = `/poll/${id}`;
};
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-8", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: "P2P Poll" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "rounded-lg border border-gray-200 bg-white p-6 shadow-sm", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "mb-4 text-lg font-semibold", children: "Create a New Poll" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
type: "text",
value: title,
onChange: (e) => setTitle(e.target.value),
onKeyDown: (e) => e.key === "Enter" && handleCreate(),
placeholder: "Enter poll title...",
className: "flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: handleCreate,
className: "rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700",
children: "Create"
}
)
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "rounded-lg border border-gray-200 bg-white p-6 shadow-sm", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "mb-4 text-lg font-semibold", children: "Join an Existing Poll" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
type: "text",
value: joinId,
onChange: (e) => setJoinId(e.target.value),
onKeyDown: (e) => e.key === "Enter" && handleJoin(),
placeholder: "Paste poll ID or link...",
className: "flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
onClick: handleJoin,
className: "rounded-md bg-gray-600 px-4 py-2 text-sm font-medium text-white hover:bg-gray-700",
children: "Join"
}
)
] })
] })
] });
}
const export_5ce7e027532e = {
default: HomeClient
};
export {
export_5ce7e027532e
};

39
dist/server/ssr/assets/poll-R5-eIJ_b.js vendored Normal file
View File

@@ -0,0 +1,39 @@
function createPoll(title) {
return {
title,
options: []
};
}
function addOption(poll, text) {
poll.options.push({
id: crypto.randomUUID(),
text,
votes: []
});
}
function hasVoted(poll, optionId, peerId) {
const option = poll.options.find((o) => o.id === optionId);
if (!option) return false;
return option.votes.includes(peerId);
}
function vote(poll, optionId, peerId) {
const option = poll.options.find((o) => o.id === optionId);
if (!option) return;
if (option.votes.includes(peerId)) return;
option.votes.push(peerId);
}
function unvote(poll, optionId, peerId) {
const option = poll.options.find((o) => o.id === optionId);
if (!option) return;
const idx = option.votes.indexOf(peerId);
if (idx !== -1) {
option.votes.splice(idx, 1);
}
}
export {
addOption as a,
createPoll as c,
hasVoted as h,
unvote as u,
vote as v
};

5494
dist/server/ssr/assets/repo-zy9lifAg.js vendored Normal file

File diff suppressed because it is too large Load Diff

11296
dist/server/ssr/index.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;

View File

@@ -1,58 +0,0 @@
"use client";
import { useEffect, useRef, useState } from "react";
import Peer from "peerjs";
export default function usePeerManager() {
const [peerId, setPeerId] = useState<string | null>(null);
const [peers, setPeers] = useState<string[]>([]);
const peerRef = useRef<Peer | null>(null);
const connectionsRef = useRef<Map<string, any>>(new Map());
useEffect(() => {
const peer = new Peer();
peerRef.current = peer;
peer.on("open", (id) => {
setPeerId(id);
});
peer.on("connection", (conn) => {
conn.on("open", () => {
connectionsRef.current.set(conn.peer, conn);
setPeers(Array.from(connectionsRef.current.keys()));
});
conn.on("data", (data) => {
console.log("Received:", data);
});
conn.on("close", () => {
connectionsRef.current.delete(conn.peer);
setPeers(Array.from(connectionsRef.current.keys()));
});
});
return () => {
peer.destroy();
};
}, []);
const connectToPeer = (id: string) => {
if (!peerRef.current) return;
const conn = peerRef.current.connect(id);
conn.on("open", () => {
connectionsRef.current.set(conn.peer, conn);
setPeers(Array.from(connectionsRef.current.keys()));
});
};
const broadcast = (data: any) => {
connectionsRef.current.forEach((conn) => {
if (conn.open) conn.send(data);
});
};
return { peerId, peers, connectToPeer, broadcast };
}

View File

@@ -1,68 +0,0 @@
"use client";
import { useState } from "react";
type Option = {
id: string;
text: string;
votes: number;
voters: string[];
};
type Poll = {
id: string;
question: string;
options: Option[];
};
export default function usePollManager(peerManager: any) {
const [poll, setPoll] = useState<Poll | null>(null);
const [myVote, setMyVote] = useState<string | null>(null);
const createPoll = (question: string, options: string[]) => {
const newPoll: Poll = {
id: Date.now().toString(),
question,
options: options.map((opt, i) => ({
id: `opt-${i}`,
text: opt,
votes: 0,
voters: [],
})),
};
setPoll(newPoll);
peerManager.broadcast({ type: "poll", poll: newPoll });
};
const vote = (optionId: string, peerId: string) => {
if (!poll) return;
const updated = { ...poll };
updated.options.forEach((opt) => {
const index = opt.voters.indexOf(peerId);
if (index !== -1) {
opt.voters.splice(index, 1);
opt.votes--;
}
});
const option = updated.options.find((o) => o.id === optionId);
if (!option) return;
option.votes++;
option.voters.push(peerId);
setPoll(updated);
setMyVote(optionId);
peerManager.broadcast({
type: "vote",
optionId,
voterId: peerId,
});
};
return { poll, createPoll, vote, myVote };
}

View File

@@ -1,9 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
turbopackFileSystemCacheForDev: true,
}
};
export default nextConfig;

6660
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,32 @@
{ {
"name": "p2p-polling", "name": "p2p-poll-app",
"version": "0.1.0", "version": "0.0.0",
"type": "module",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "waku dev",
"build": "next build", "build": "waku build",
"start": "next start", "start": "waku start"
"lint": "eslint"
}, },
"dependencies": { "dependencies": {
"next": "16.2.0", "@automerge/automerge": "^3.2.4",
"peerjs": "^1.5.5", "@automerge/automerge-repo": "^2.5.3",
"react": "19.2.4", "@automerge/automerge-repo-network-broadcastchannel": "^2.5.3",
"react-dom": "19.2.4" "@automerge/automerge-repo-network-websocket": "^2.5.3",
"@automerge/automerge-repo-react-hooks": "^2.5.3",
"@automerge/automerge-repo-storage-indexeddb": "^2.5.3",
"@tailwindcss/vite": "^4.2.1",
"react": "~19.2.4",
"react-dom": "~19.2.4",
"react-server-dom-webpack": "~19.2.4",
"tailwindcss": "^4.2.1",
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-wasm": "^3.5.0",
"waku": "1.0.0-alpha.5"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@types/react": "^19.2.14",
"@types/node": "^20", "@types/react-dom": "^19.2.3",
"@types/react": "^19", "typescript": "^5.9.3"
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.2.0",
"tailwindcss": "^4",
"typescript": "^5"
} }
} }

View File

@@ -1,7 +0,0 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

View File

@@ -1 +0,0 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 391 B

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 128 B

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

Before

Width:  |  Height:  |  Size: 385 B

View File

@@ -0,0 +1,37 @@
"use client";
import { useRepo } from "@automerge/automerge-repo-react-hooks";
import { useEffect, useState, useCallback } from "react";
export default function ConnectionStatus() {
const repo = useRepo();
const [connected, setConnected] = useState(false);
const updateStatus = useCallback(() => {
setConnected(repo ? repo.peers.length > 0 : false);
}, [repo]);
useEffect(() => {
updateStatus();
const onChange = () => updateStatus();
repo.networkSubsystem.on("peer", onChange);
repo.networkSubsystem.on("peer-disconnected", onChange);
return () => {
repo.networkSubsystem.off("peer", onChange);
repo.networkSubsystem.off("peer-disconnected", onChange);
};
}, [repo, updateStatus]);
return (
<div className="flex items-center gap-2 text-xs text-gray-500">
<span
className={`inline-block h-2 w-2 rounded-full ${
connected ? "bg-green-500" : "bg-yellow-500"
}`}
/>
{connected ? "Connected" : "Connecting..."}
</div>
);
}

View File

@@ -0,0 +1,79 @@
"use client";
import { useState } from "react";
import { useRouter } from "waku";
import { useRepo } from "@automerge/automerge-repo-react-hooks";
import { createPoll } from "../lib/poll.js";
import type { Poll } from "../lib/types.js";
export default function HomeClient() {
const [title, setTitle] = useState("");
const [joinId, setJoinId] = useState("");
const repo = useRepo();
const router = useRouter();
const handleCreate = () => {
if (!title.trim()) return;
const handle = repo.create<Poll>();
handle.change((doc) => {
const poll = createPoll(title.trim());
doc.title = poll.title;
doc.options = poll.options;
});
router.push(`/poll/${handle.documentId}`);
};
const handleJoin = () => {
const id = joinId.trim();
if (!id) return;
router.push(`/poll/${id}`);
};
return (
<div className="space-y-8">
<title>P2P Poll</title>
{/* Create Poll */}
<section className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
<h2 className="mb-4 text-lg font-semibold">Create a New Poll</h2>
<div className="flex gap-2">
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleCreate()}
placeholder="Enter poll title..."
className="flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
<button
onClick={handleCreate}
className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
>
Create
</button>
</div>
</section>
{/* Join Poll */}
<section className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
<h2 className="mb-4 text-lg font-semibold">Join an Existing Poll</h2>
<div className="flex gap-2">
<input
type="text"
value={joinId}
onChange={(e) => setJoinId(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleJoin()}
placeholder="Paste poll ID or link..."
className="flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
<button
onClick={handleJoin}
className="rounded-md bg-gray-600 px-4 py-2 text-sm font-medium text-white hover:bg-gray-700"
>
Join
</button>
</div>
</section>
</div>
);
}

View File

@@ -0,0 +1,28 @@
"use client";
import { isValidAutomergeUrl } from "@automerge/automerge-repo";
import PollView from "./PollView.js";
export default function PollPageClient({ id }: { id: string }) {
const automergeUrl = `automerge:${id}` as const;
if (!isValidAutomergeUrl(automergeUrl)) {
return (
<div className="rounded-lg border border-red-200 bg-red-50 p-6 text-center">
<h2 className="text-lg font-semibold text-red-800">Invalid Poll ID</h2>
<p className="mt-2 text-sm text-red-600">
The poll ID in the URL is not valid.
</p>
<a href="/" className="mt-4 inline-block text-sm text-blue-600 hover:underline">
Go back home
</a>
</div>
);
}
return (
<div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
<PollView docUrl={automergeUrl} />
</div>
);
}

140
src/components/PollView.tsx Normal file
View File

@@ -0,0 +1,140 @@
"use client";
import { useState, useMemo } from "react";
import type { AutomergeUrl } from "@automerge/automerge-repo";
import { useDocument } from "@automerge/automerge-repo-react-hooks";
import { addOption, vote, unvote, hasVoted } from "../lib/poll.js";
import { getPeerId } from "../lib/peer.js";
import type { Poll } from "../lib/types.js";
import ConnectionStatus from "./ConnectionStatus.js";
export default function PollView({ docUrl }: { docUrl: AutomergeUrl }) {
const [doc, changeDoc] = useDocument<Poll>(docUrl);
const [newOption, setNewOption] = useState("");
const [copied, setCopied] = useState(false);
const peerId = useMemo(() => getPeerId(), []);
if (!doc) {
return (
<div className="flex items-center justify-center py-12">
<div className="text-gray-500">Loading poll...</div>
</div>
);
}
const totalVotes = doc.options.reduce((sum, o) => sum + o.votes.length, 0);
const handleAddOption = () => {
const text = newOption.trim();
if (!text) return;
changeDoc((d) => addOption(d, text));
setNewOption("");
};
const handleVote = (optionId: string) => {
if (hasVoted(doc, optionId, peerId)) {
changeDoc((d) => unvote(d, optionId, peerId));
} else {
changeDoc((d) => vote(d, optionId, peerId));
}
};
const handleCopy = () => {
const url = window.location.href;
navigator.clipboard.writeText(url).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}).catch(() => {});
};
return (
<div className="space-y-6">
<div className="flex items-start justify-between">
<div>
<h2 className="text-2xl font-bold">{doc.title}</h2>
<p className="mt-1 text-sm text-gray-500">
{totalVotes} vote{totalVotes !== 1 ? "s" : ""} total
</p>
</div>
<ConnectionStatus />
</div>
{/* Options list */}
<div className="space-y-3">
{doc.options.map((option) => {
const voted = hasVoted(doc, option.id, peerId);
const pct = totalVotes > 0 ? (option.votes.length / totalVotes) * 100 : 0;
return (
<div
key={option.id}
className="relative overflow-hidden rounded-lg border border-gray-200 bg-white"
>
{/* Vote bar */}
<div
className="absolute inset-y-0 left-0 bg-blue-50 transition-all duration-300"
style={{ width: `${pct}%` }}
/>
<div className="relative flex items-center justify-between px-4 py-3">
<button
onClick={() => handleVote(option.id)}
className={`flex items-center gap-2 text-left text-sm font-medium ${
voted ? "text-blue-600" : "text-gray-700 hover:text-blue-600"
}`}
>
<span
className={`flex h-5 w-5 items-center justify-center rounded border text-xs ${
voted
? "border-blue-600 bg-blue-600 text-white"
: "border-gray-300"
}`}
>
{voted ? "\u2713" : ""}
</span>
{option.text}
</button>
<span className="text-sm text-gray-500">
{option.votes.length} ({pct.toFixed(0)}%)
</span>
</div>
</div>
);
})}
{doc.options.length === 0 && (
<p className="py-4 text-center text-sm text-gray-400">
No options yet. Add one below!
</p>
)}
</div>
{/* Add option form */}
<div className="flex gap-2">
<input
type="text"
value={newOption}
onChange={(e) => setNewOption(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleAddOption()}
placeholder="Add an option..."
className="flex-1 rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
<button
onClick={handleAddOption}
className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
>
Add
</button>
</div>
{/* Share */}
<div className="border-t border-gray-200 pt-4">
<button
onClick={handleCopy}
className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-50"
>
{copied ? "Copied!" : "Copy shareable link"}
</button>
</div>
</div>
);
}

View File

@@ -0,0 +1,45 @@
"use client";
import { type ReactNode, useState, useEffect } from "react";
import { RepoContext } from "@automerge/automerge-repo-react-hooks";
import type { Repo } from "@automerge/automerge-repo";
export default function Providers({ children }: { children: ReactNode }) {
const [repo, setRepo] = useState<Repo | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cleanup: (() => void) | undefined;
let handleBeforeUnload: (() => void) | undefined;
import("../lib/repo.js").then(({ getRepo, cleanupRepo }) => {
const r = getRepo();
setRepo(r);
cleanup = cleanupRepo;
handleBeforeUnload = () => {
r.networkSubsystem.adapters.forEach((adapter) => adapter.disconnect());
};
window.addEventListener("beforeunload", handleBeforeUnload);
}).catch(() => {
setError("Failed to initialize. Please refresh the page.");
});
return () => {
if (handleBeforeUnload) {
window.removeEventListener("beforeunload", handleBeforeUnload);
}
cleanup?.();
};
}, []);
if (error) {
return <div className="flex min-h-screen items-center justify-center text-red-500">{error}</div>;
}
if (!repo) {
return <div className="flex min-h-screen items-center justify-center text-gray-400">Loading...</div>;
}
return <RepoContext.Provider value={repo}>{children}</RepoContext.Provider>;
}

View File

@@ -0,0 +1,56 @@
import { describe, test, expect, beforeEach } from "bun:test";
// Mock localStorage
const storage = new Map<string, string>();
const localStorageMock = {
getItem: (key: string) => storage.get(key) ?? null,
setItem: (key: string, value: string) => storage.set(key, value),
removeItem: (key: string) => storage.delete(key),
clear: () => storage.clear(),
get length() {
return storage.size;
},
key: (_index: number) => null,
};
Object.defineProperty(globalThis, "localStorage", {
value: localStorageMock,
writable: true,
});
// Import after mock is set up
const { getPeerId } = await import("../peer.js");
describe("getPeerId", () => {
beforeEach(() => {
storage.clear();
});
test("persists and returns the same ID on subsequent calls", () => {
const id1 = getPeerId();
const id2 = getPeerId();
expect(id1).toBe(id2);
});
test("stores the ID in localStorage", () => {
const id = getPeerId();
expect(storage.get("p2p-poll-peer-id")).toBe(id);
});
test("returns a new UUID each time when localStorage is unavailable", () => {
const saved = globalThis.localStorage;
// @ts-expect-error — deliberately removing localStorage to test fallback
globalThis.localStorage = undefined;
const id1 = getPeerId();
const id2 = getPeerId();
expect(id1).not.toBe(id2);
expect(id1).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
);
// Restore
globalThis.localStorage = saved;
});
});

View File

@@ -0,0 +1,94 @@
import { describe, test, expect } from "bun:test";
import { createPoll, addOption, vote, unvote, hasVoted } from "../poll.js";
describe("vote", () => {
test("prevents double-vote (no duplicate entries)", () => {
const poll = createPoll("Test");
addOption(poll, "Red");
const optionId = poll.options[0]!.id;
vote(poll, optionId, "peer-1");
vote(poll, optionId, "peer-1");
expect(poll.options[0]!.votes).toEqual(["peer-1"]);
expect(poll.options[0]!.votes).toHaveLength(1);
});
test("allows different peers to vote", () => {
const poll = createPoll("Test");
addOption(poll, "Red");
const optionId = poll.options[0]!.id;
vote(poll, optionId, "peer-1");
vote(poll, optionId, "peer-2");
expect(poll.options[0]!.votes).toEqual(["peer-1", "peer-2"]);
});
test("is a no-op for non-existent option ID", () => {
const poll = createPoll("Test");
addOption(poll, "Red");
vote(poll, "non-existent-id", "peer-1");
expect(poll.options[0]!.votes).toEqual([]);
});
test("voting on one option does not affect another option", () => {
const poll = createPoll("Test");
addOption(poll, "Red");
addOption(poll, "Blue");
const redId = poll.options[0]!.id;
const blueId = poll.options[1]!.id;
vote(poll, redId, "peer-1");
expect(poll.options[0]!.votes).toEqual(["peer-1"]);
expect(poll.options[1]!.votes).toEqual([]);
vote(poll, blueId, "peer-2");
expect(poll.options[0]!.votes).toEqual(["peer-1"]);
expect(poll.options[1]!.votes).toEqual(["peer-2"]);
});
});
describe("unvote", () => {
test("removes peer ID from votes array", () => {
const poll = createPoll("Test");
addOption(poll, "Red");
const optionId = poll.options[0]!.id;
vote(poll, optionId, "peer-1");
unvote(poll, optionId, "peer-1");
expect(poll.options[0]!.votes).toEqual([]);
});
test("does nothing if peer has not voted", () => {
const poll = createPoll("Test");
addOption(poll, "Red");
const optionId = poll.options[0]!.id;
unvote(poll, optionId, "peer-1");
expect(poll.options[0]!.votes).toEqual([]);
});
});
describe("hasVoted", () => {
test("returns true when peer has voted", () => {
const poll = createPoll("Test");
addOption(poll, "Red");
const optionId = poll.options[0]!.id;
vote(poll, optionId, "peer-1");
expect(hasVoted(poll, optionId, "peer-1")).toBe(true);
});
test("returns false for non-existent option", () => {
const poll = createPoll("Test");
expect(hasVoted(poll, "non-existent", "peer-1")).toBe(false);
});
});

22
src/lib/peer.ts Normal file
View File

@@ -0,0 +1,22 @@
const PEER_ID_KEY = "p2p-poll-peer-id";
/** Generate a UUID v4 */
function generateUUID(): string {
return crypto.randomUUID();
}
/**
* Get or create a persistent peer ID.
* Stored in localStorage so each browser tab/device gets a stable identity.
*/
export function getPeerId(): string {
if (typeof globalThis.localStorage === "undefined") {
return generateUUID();
}
let id = localStorage.getItem(PEER_ID_KEY);
if (!id) {
id = generateUUID();
localStorage.setItem(PEER_ID_KEY, id);
}
return id;
}

43
src/lib/poll.ts Normal file
View File

@@ -0,0 +1,43 @@
import type { Poll } from "./types.js";
/** Create a new poll document with the given title and no options */
export function createPoll(title: string): Poll {
return {
title,
options: [],
};
}
/** Add a new option to the poll */
export function addOption(poll: Poll, text: string): void {
poll.options.push({
id: crypto.randomUUID(),
text,
votes: [],
});
}
/** Check if a peer has already voted on an option */
export function hasVoted(poll: Poll, optionId: string, peerId: string): boolean {
const option = poll.options.find((o) => o.id === optionId);
if (!option) return false;
return option.votes.includes(peerId);
}
/** Add a vote for the given option. Prevents double-voting. */
export function vote(poll: Poll, optionId: string, peerId: string): void {
const option = poll.options.find((o) => o.id === optionId);
if (!option) return;
if (option.votes.includes(peerId)) return;
option.votes.push(peerId);
}
/** Remove a vote from the given option */
export function unvote(poll: Poll, optionId: string, peerId: string): void {
const option = poll.options.find((o) => o.id === optionId);
if (!option) return;
const idx = option.votes.indexOf(peerId);
if (idx !== -1) {
option.votes.splice(idx, 1);
}
}

29
src/lib/repo.ts Normal file
View File

@@ -0,0 +1,29 @@
import { Repo } from "@automerge/automerge-repo";
import { BroadcastChannelNetworkAdapter } from "@automerge/automerge-repo-network-broadcastchannel";
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb";
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
let repo: Repo | null = null;
/** Get or create the singleton Automerge Repo instance (browser-only) */
export function getRepo(): Repo {
if (repo) return repo;
repo = new Repo({
network: [
new BrowserWebSocketClientAdapter("wss://sync.automerge.org"),
new BroadcastChannelNetworkAdapter(),
],
storage: new IndexedDBStorageAdapter("p2p-poll-app"),
});
return repo;
}
/** Shut down the repo and reset the singleton */
export function cleanupRepo(): void {
if (repo) {
repo.shutdown();
repo = null;
}
}

12
src/lib/types.ts Normal file
View File

@@ -0,0 +1,12 @@
/** A single poll option that users can vote on */
export interface PollOption {
id: string;
text: string;
votes: string[]; // Array of peer IDs
}
/** The shared poll document stored in Automerge */
export interface Poll {
title: string;
options: PollOption[];
}

27
src/pages.gen.ts Normal file
View File

@@ -0,0 +1,27 @@
// deno-fmt-ignore-file
// biome-ignore format: generated types do not need formatting
// prettier-ignore
import type { PathsForPages, GetConfigResponse } from 'waku/router';
// prettier-ignore
import type { getConfig as File_Root_getConfig } from './pages/_root';
// prettier-ignore
import type { getConfig as File_Index_getConfig } from './pages/index';
// prettier-ignore
import type { getConfig as File_PollId_getConfig } from './pages/poll/[id]';
// prettier-ignore
type Page =
| ({ path: '/_root' } & GetConfigResponse<typeof File_Root_getConfig>)
| ({ path: '/' } & GetConfigResponse<typeof File_Index_getConfig>)
| ({ path: '/poll/[id]' } & GetConfigResponse<typeof File_PollId_getConfig>);
// prettier-ignore
declare module 'waku/router' {
interface RouteConfig {
paths: PathsForPages<Page>;
}
interface CreatePagesConfig {
pages: Page;
}
}

24
src/pages/_layout.tsx Normal file
View File

@@ -0,0 +1,24 @@
import type { ReactNode } from "react";
import "../styles/global.css";
import Providers from "../components/Providers.js";
const Layout = ({ children }: { children: ReactNode }) => (
<Providers>
<div className="min-h-screen bg-gray-50 text-gray-900">
<header className="border-b border-gray-200 bg-white px-4 py-3">
<h1 className="text-xl font-bold">
<a href="/" className="hover:text-blue-600">
P2P Poll
</a>
</h1>
</header>
<main className="mx-auto max-w-xl px-4 py-8">{children}</main>
</div>
</Providers>
);
export default Layout;
export const getConfig = async () => {
return { render: "static" } as const;
};

17
src/pages/_root.tsx Normal file
View File

@@ -0,0 +1,17 @@
import type { ReactNode } from "react";
export default function Root({ children }: { children: ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>{children}</body>
</html>
);
}
export const getConfig = () => {
return { render: "dynamic" } as const;
};

9
src/pages/index.tsx Normal file
View File

@@ -0,0 +1,9 @@
import HomeClient from "../components/HomeClient.js";
export default async function HomePage() {
return <HomeClient />;
}
export const getConfig = async () => {
return { render: "dynamic" } as const;
};

10
src/pages/poll/[id].tsx Normal file
View File

@@ -0,0 +1,10 @@
import type { PageProps } from "waku/router";
import PollPageClient from "../../components/PollPageClient.js";
export default async function PollPage({ id }: PageProps<"/poll/[id]">) {
return <PollPageClient id={id} />;
}
export const getConfig = async () => {
return { render: "dynamic" } as const;
};

1
src/styles/global.css Normal file
View File

@@ -0,0 +1 @@
@import "tailwindcss";

View File

@@ -1,34 +1,17 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true, "strict": true,
"target": "esnext",
"noEmit": true, "noEmit": true,
"isolatedModules": true,
"moduleDetection": "force",
"downlevelIteration": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true, "skipLibCheck": true,
"isolatedModules": true, "noUncheckedIndexedAccess": true,
"jsx": "react-jsx", "exactOptionalPropertyTypes": true,
"incremental": true, "jsx": "react-jsx"
"plugins": [ }
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
} }

10
waku.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import tailwindcss from "@tailwindcss/vite";
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
import { defineConfig } from "waku/config";
export default defineConfig({
vite: {
plugins: [tailwindcss(), wasm(), topLevelAwait()],
},
});