diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..fddba74 --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,58 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build static site + run: npm run build + env: + VITE_BASE_PATH: /${{ github.event.repository.name }}/ + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: pubky-live-vote/dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c2a6f9c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1798 @@ +{ + "name": "pubky-vote-hackathon-2025", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pubky-vote-hackathon-2025", + "workspaces": [ + "pubky-live-vote" + ] + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@synonymdev/pubky": { + "version": "0.6.0-rc.6", + "resolved": "https://registry.npmjs.org/@synonymdev/pubky/-/pubky-0.6.0-rc.6.tgz", + "integrity": "sha512-LRUPlRle/sDejtd0Bg7BTBKWlTmTzzYbR2ZDeiA3AkpUHRsh139iYoOCtuALuT4rtRfRVuEw15VeKkApgjzE4Q==", + "license": "MIT", + "dependencies": { + "fetch-cookie": "^3.0.1" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", + "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.19.tgz", + "integrity": "sha512-zoKGUdu6vb2jd3YOq0nnhEDQVbPcHhco3UImJrv5dSkvxTc2pl2WjOPsjZXDwPDSl5eghIMuY3R6J9NDKF3KcQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.239", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.239.tgz", + "integrity": "sha512-1y5w0Zsq39MSPmEjHjbizvhYoTaulVtivpxkp5q5kaPmQtsK6/2nvAzGRxNMS9DoYySp9PkW0MAQDwU1m764mg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fetch-cookie": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-3.1.0.tgz", + "integrity": "sha512-s/XhhreJpqH0ftkGVcQt8JE9bqk+zRn4jF5mPJXWZeQMCI5odV9K+wEWYbnzFPHgQZlvPSMjS4n4yawWE8RINw==", + "license": "Unlicense", + "dependencies": { + "set-cookie-parser": "^2.4.8", + "tough-cookie": "^5.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/pubky-live-vote": { + "resolved": "pubky-live-vote", + "link": true + }, + "node_modules/qr.js": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", + "integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-qr-code": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.18.tgz", + "integrity": "sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1", + "qr.js": "0.0.0" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "pubky-live-vote": { + "version": "0.1.0", + "dependencies": { + "@synonymdev/pubky": "0.6.0-rc.6", + "dayjs": "^1.11.10", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-qr-code": "^2.0.12" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.3", + "vite": "^5.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ca85b78 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "pubky-vote-hackathon-2025", + "private": true, + "workspaces": [ + "pubky-live-vote" + ], + "scripts": { + "dev": "npm run dev --workspace=pubky-live-vote", + "build": "npm run build --workspace=pubky-live-vote", + "preview": "npm run preview --workspace=pubky-live-vote", + "install:app": "npm install --prefix pubky-live-vote", + "signup": "node scripts/signup.mjs", + "authenticator": "node scripts/authenticator.mjs" + } +} diff --git a/pubky-live-vote/LICENSE b/pubky-live-vote/LICENSE new file mode 100644 index 0000000..86d572e --- /dev/null +++ b/pubky-live-vote/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Pubky Live Vote contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pubky-live-vote/README.md b/pubky-live-vote/README.md new file mode 100644 index 0000000..056b8f3 --- /dev/null +++ b/pubky-live-vote/README.md @@ -0,0 +1,176 @@ +# Pubky Live Vote + +image + +Pubky Live Vote is a responsive hackathon voting interface that uses the Pubky JavaScript SDK for authentication and ballot storage. It is designed for 1–2 day events where dozens of voters need to authenticate quickly, score projects across the official rubric, provide feedback, and watch the leaderboard update in real time. + +## Features + +- **Pubky authentication** – voters connect through the Pubky Ring QR flow. A mock client is bundled for offline development and falls back automatically if the SDK is unavailable. +- **Mobile and desktop friendly UI** – adaptive layout for phones, tablets, and desktops with high-contrast styling. +- **Full rubric support** – sliders for Complexity, Creativity / Practicality, Team Presentation, and Feedback Quality plus a readiness toggle. +- **Popular vote ranking** – drag-friendly ranking board with explicit self-vote blocking once the voter selects their own project. +- **Feedback & tagging** – comment box and quick tag helper per project. +- **Offline cache** – ballots queue in local storage whenever the network is down and flush automatically once connectivity is restored. +- **Live leaderboard** – polls the Pubky homeserver summary (or aggregates ballots directly) every 10 seconds, showing the voter count and data source for transparency. + +## Architecture overview + +Pubky Live Vote is a single-page Vite + React application that talks directly to the Pubky JavaScript SDK. The architecture is intentionally thin—everything runs client-side so hackathon organisers can deploy the app as static files while still benefiting from secure storage through Pubky. + +### Core modules + +| Layer | Key modules | Responsibilities | +| --- | --- | --- | +| **UI** | `src/components/*` | Presentation components for ballot entry, leaderboard, navigation chrome, and responsive layouts. Each component receives state via props or React context to keep rendering predictable. | +| **State & context** | `src/context/AuthContext`, `src/context/ProjectsContext` | Centralises session, project catalogue, and submission state. Auth context exposes the connected Pubky identity plus loading/error states. Projects context provides rubric metadata, cached ballots, and helper actions. | +| **Services** | `src/services/pubkyClient`, `src/services/offlineQueue`, `src/services/sampleData` | Wrap Pubky SDK calls (login, ballot CRUD, summary fetch) and isolate network logic. The offline queue abstracts localStorage persistence, while sample data powers the preview mode used before the homeserver is reachable. | +| **Types & utilities** | `src/types`, `src/services/scoring` | Declare the shared interfaces for ballots, rubric weights, and leaderboard entries, plus pure functions to calculate weighted scores and tie-breakers. | + +### Runtime data flow + +1. **Bootstrap** – when the app mounts, it loads configuration from `window.__PUBKY_CONFIG__`, instantiates the Pubky SDK (or the mocked client), and pulls the current project list plus any cached ballots. +2. **Authentication** – the login dialog triggers a Pubky Ring session. Auth context resolves to either a verified Pubky identity or the offline mock identity so users can continue testing. +3. **Ballot capture** – the ballot form emits updates into local React state; on submit the service layer signs and uploads the payload to `pubky-live-vote/ballots/.json` via the SDK. When offline, submissions append to the queue and display a pending badge. +4. **Leaderboard refresh** – a polling effect queries `summary.json`. When the summary endpoint fails, the app composes the leaderboard from cached ballots and marks the data source accordingly. +5. **Feedback loop** – successful submissions rehydrate the local cache, prune duplicates, and broadcast the update to any open tabs using the browser Broadcast Channel API so multiple screens stay in sync. + +### Build & deployment pipeline + +1. Developers run `npm run dev` for a hot-reloading Vite server with the mock Pubky client prewired. +2. `npm run build` performs a production build and TypeScript type checking, emitting static assets in `dist/`. +3. GitHub Actions (see [`.github/workflows/deploy-pages.yml`](../.github/workflows/deploy-pages.yml)) runs on pushes to `main`, builds the app, and publishes `dist/` to GitHub Pages with a correctly scoped base path. +4. Organisers can host the same `dist/` bundle on any static hosting provider; no server-side component is required beyond the Pubky homeserver that stores ballots. + +## Voting lifecycle + +The diagram below summarises a typical voter session: + +1. **Discovery** – voter opens the published URL from a QR code or shared link. +2. **Join** – the app requests a login session; the voter scans the Pubky Ring QR code or, if unavailable, uses the mock client to simulate approval. +3. **Score** – projects load with rubric sliders, comment fields, and quick tags. The UI validates input ranges and blocks submission until every mandatory criterion is rated. +4. **Submit** – once the user presses **Submit ballot**, the app signs the payload and attempts to push it to the configured homeserver. Any failure routes the ballot into the offline queue. +5. **Sync** – the background worker retries queued ballots whenever connectivity returns and posts toast notifications for each success. +6. **Leaderboard** – after a ballot lands, the leaderboard refreshes to show the updated totals and ranking with context on the data source (summary vs. locally aggregated ballots). + +## Development process & friction log + +During the hackathon build we followed a tight feedback loop: + +1. **Prototype UX** – sketched the mobile-first ballot flow in Figma, then stubbed the React components using static data. +2. **Integrate Pubky** – wired the SDK into the mock client first, swapped to the staging homeserver, and validated auth + storage flows end-to-end. +3. **Harden offline mode** – implemented the queue, background flush, and conflict resolution before styling polish to guarantee ballot resilience. +4. **Polish & QA** – ran through event-day scripts (onboarding voters, resetting ballots, viewing standings) with organisers to ensure the copy and state transitions made sense. + +### Friction points + +- **Invitation code handling** – staging homeserver login required manual token generation, slowing down new device onboarding. Mitigation: added the CLI helper (`npm run signup`) and documented the admin endpoint in this README. +- **SDK typings lag** – the 0.6.0-rc.6 TypeScript definitions were missing a few optional fields (notably around summary responses), forcing us to extend types locally. We upstreamed the feedback to the Pubky core team. +- **Mobile keyboard overlap** – iOS Safari covered the lower rubric sliders during testing. The fix was to implement viewport-height safe area CSS variables and adjust scroll anchoring in the ballot component. +- **Leaderboard polling limits** – aggressive polling tripped rate limiting on early builds. We introduced exponential backoff and a circuit breaker that switches to local aggregation after repeated failures. + +## Getting started + +### Prerequisites + +- Node.js 18+ +- npm 9+ + +Optional: install the Pubky homeserver testnet tools if you want to target a local network. + +### Installation + +```bash +cd pubky-live-vote +npm install +``` + +> **Tip:** if you prefer to stay at the repository root, the workspace wrapper also lets you run `npm run install:app` to install +> dependencies without changing directories. + +### Development + +```bash +npm run dev +``` + +From the repository root you can use `npm run dev` as well—the root `package.json` proxies the command to the app workspace. + +The dev server runs on [http://localhost:5173](http://localhost:5173). The first load will automatically request a Pubky Ring session and render the QR code. Scan it with the Ring mobile app to authenticate. Without the app you can use the bundled mock client, which auto-accepts the login after a short delay. + +For convenience, local development now serves a baked-in leaderboard snapshot and ballot history from `/pubky-live-vote/`. This means the leaderboard and activity panels light up immediately without relying on the remote staging homeserver. If you prefer to point at a live homeserver you can still override the target using the configuration snippet below. + +### Production build + +```bash +npm run build +``` + +The build command compiles the Vite project and performs type checking. + +### Homeserver signup & authenticator helpers + +Two convenience scripts wrap the Pubky SDK for command-line flows that require a recovery file: + +```bash +# Create or update a user on the target homeserver using a recovery file +npm run signup -- [invitation_code] [--testnet] + +# Approve a pubkyauth:// login request with a recovery file +npm run authenticator -- "" [--testnet] [--homeserver ] +``` + +Both commands prompt for the recovery passphrase. The `--testnet` flag switches to the SDK testnet instance and skips the invitation requirement. + +### Configuring the homeserver + +By default the client tries to create a Pubky SDK instance against the staging homeserver and falls back to the local mock client. To explicitly point at a local testnet you can expose `window.__PUBKY_CONFIG__` before the bundle loads: + +```html + +``` + +Then, update the login flow to trust ballots signed by that server. All ballots are stored under `pubky-live-vote/ballots/.json`. + +### Folder structure + +``` +src/ + components/ # UI components and styling + context/ # Auth and project state providers + services/ # Pubky client adapter, offline queue, sample data + types/ # Shared TypeScript types +``` + +## Testing the offline queue + +1. Open the dev server and authenticate. +2. Fill out a few scores and press **Submit ballot**. +3. Toggle your browser to offline mode and make more changes. +4. Submit again. The queue will store the ballot locally. +5. Reconnect; the app auto-flushes pending ballots and refreshes the submission timestamp. + +## Live leaderboard data flow + +1. The client requests `pubky-live-vote/summary.json` from the configured homeserver every 10 seconds. If the summary is offline, it falls back to `pubky-live-vote/ballots/index.json` and aggregates the raw ballots locally. +2. Each project’s component scores are normalised to a 0–100 range, the official weights are applied, and tie-ready totals are produced. The UI highlights whether the snapshot came from the summary file, raw ballots, or a local preview. +3. The table updates in place without a page reload, giving organisers and participants a reliable view of standings as ballots from up to 24 voters (and beyond) land during the event. + +## Deploying to GitHub Pages + +A ready-to-use GitHub Actions workflow lives in [`.github/workflows/deploy-pages.yml`](../.github/workflows/deploy-pages.yml). It builds the Vite app, packages the static output from `pubky-live-vote/dist`, and publishes it to the repository’s GitHub Pages environment. To go live: + +1. Push the workflow to your fork or repository and make sure the default branch is `main` (or update the workflow trigger). +2. In **Settings → Pages**, select **GitHub Actions** as the source. +3. Merge to `main` or run the workflow manually from the **Actions** tab. The job automatically sets the correct base path so the app works from `https://.github.io//` on both desktop and mobile browsers. + +If you need to serve the site from a custom subdirectory, override the `VITE_BASE_PATH` environment variable in the **Build static site** step of the workflow. For custom domains, configure the CNAME record in the repository settings after the first deployment completes. + +## License + +This project is released under the MIT license. See [LICENSE](./LICENSE). diff --git a/pubky-live-vote/index.html b/pubky-live-vote/index.html new file mode 100644 index 0000000..d5c81a3 --- /dev/null +++ b/pubky-live-vote/index.html @@ -0,0 +1,12 @@ + + + + + + Pubky Live Vote + + +
+ + + diff --git a/pubky-live-vote/package.json b/pubky-live-vote/package.json new file mode 100644 index 0000000..c5ed437 --- /dev/null +++ b/pubky-live-vote/package.json @@ -0,0 +1,25 @@ +{ + "name": "pubky-live-vote", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@synonymdev/pubky": "0.6.0-rc.6", + "dayjs": "^1.11.10", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-qr-code": "^2.0.12" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.3", + "vite": "^5.1.0" + } +} diff --git a/pubky-live-vote/public/pubky-live-vote/ballots/index.json b/pubky-live-vote/public/pubky-live-vote/ballots/index.json new file mode 100644 index 0000000..dc753ee --- /dev/null +++ b/pubky-live-vote/public/pubky-live-vote/ballots/index.json @@ -0,0 +1,434 @@ +[ + { + "voterId": "demo-voter-1", + "submittedAt": "2025-02-15T10:00:00.000Z", + "scores": [ + { + "projectId": "pubky-mcp-server", + "scores": { + "complexity": 8, + "creativity": 7, + "presentation": 8, + "feedback": 8 + }, + "readiness": true, + "comment": "Great way to hide SDK friction.", + "tags": [ + "sdk", + "automation" + ] + }, + { + "projectId": "pubky-homeserver-cli", + "scores": { + "complexity": 7, + "creativity": 6, + "presentation": 7, + "feedback": 7 + }, + "readiness": true, + "comment": "Slick CLI coverage.", + "tags": [ + "cli", + "ops" + ] + }, + { + "projectId": "pubky-tag-extension", + "scores": { + "complexity": 6, + "creativity": 8, + "presentation": 7, + "feedback": 8 + }, + "readiness": false, + "comment": "Tagging UX is clear already.", + "tags": [ + "browser" + ] + }, + { + "projectId": "booksy", + "scores": { + "complexity": 5, + "creativity": 7, + "presentation": 8, + "feedback": 7 + }, + "readiness": false, + "comment": "Needs sync polish but promising.", + "tags": [ + "sharing" + ] + }, + { + "projectId": "pubky-cookbook", + "scores": { + "complexity": 6, + "creativity": 6, + "presentation": 9, + "feedback": 9 + }, + "readiness": true, + "comment": "Docs look ready to ship.", + "tags": [ + "docs" + ] + }, + { + "projectId": "decentralized-wiki", + "scores": { + "complexity": 7, + "creativity": 7, + "presentation": 6, + "feedback": 7 + }, + "readiness": false, + "comment": "Curious to see moderation story.", + "tags": [ + "wiki" + ] + }, + { + "projectId": "monky", + "scores": { + "complexity": 8, + "creativity": 8, + "presentation": 6, + "feedback": 6 + }, + "readiness": true, + "comment": "Contracts flow is clever.", + "tags": [ + "identity" + ] + }, + { + "projectId": "pubky-live-vote", + "scores": { + "complexity": 8, + "creativity": 9, + "presentation": 9, + "feedback": 9 + }, + "readiness": true, + "comment": "Polished end-to-end flow with clear UX.", + "tags": [ + "ux", + "realtime" + ] + }, + { + "projectId": "geostories", + "scores": { + "complexity": 7, + "creativity": 9, + "presentation": 7, + "feedback": 7 + }, + "readiness": false, + "comment": "Map interactions feel smooth.", + "tags": [ + "mapping" + ] + } + ], + "popularRanking": [ + "pubky-live-vote", + "pubky-mcp-server", + "geostories", + "pubky-cookbook", + "monky", + "pubky-homeserver-cli", + "pubky-tag-extension", + "booksy", + "decentralized-wiki" + ] + }, + { + "voterId": "demo-voter-2", + "submittedAt": "2025-02-15T10:05:00.000Z", + "scores": [ + { + "projectId": "pubky-mcp-server", + "scores": { + "complexity": 8, + "creativity": 8, + "presentation": 7, + "feedback": 8 + }, + "readiness": true, + "comment": "Great developer story.", + "tags": [ + "sdk" + ] + }, + { + "projectId": "pubky-homeserver-cli", + "scores": { + "complexity": 7, + "creativity": 7, + "presentation": 7, + "feedback": 6 + }, + "readiness": true, + "comment": "Could use more examples.", + "tags": [ + "docs" + ] + }, + { + "projectId": "pubky-tag-extension", + "scores": { + "complexity": 6, + "creativity": 8, + "presentation": 8, + "feedback": 8 + }, + "readiness": false, + "comment": "Tag filter view is neat.", + "tags": [ + "browser" + ] + }, + { + "projectId": "booksy", + "scores": { + "complexity": 5, + "creativity": 7, + "presentation": 7, + "feedback": 7 + }, + "readiness": false, + "comment": "Excited for sharing features.", + "tags": [ + "sharing" + ] + }, + { + "projectId": "pubky-cookbook", + "scores": { + "complexity": 6, + "creativity": 6, + "presentation": 9, + "feedback": 9 + }, + "readiness": true, + "comment": "Love the runnable snippets.", + "tags": [ + "docs" + ] + }, + { + "projectId": "decentralized-wiki", + "scores": { + "complexity": 7, + "creativity": 7, + "presentation": 6, + "feedback": 7 + }, + "readiness": false, + "comment": "Needs governance details.", + "tags": [ + "wiki" + ] + }, + { + "projectId": "monky", + "scores": { + "complexity": 8, + "creativity": 8, + "presentation": 7, + "feedback": 6 + }, + "readiness": true, + "comment": "Key linking is powerful.", + "tags": [ + "identity" + ] + }, + { + "projectId": "pubky-live-vote", + "scores": { + "complexity": 8, + "creativity": 9, + "presentation": 9, + "feedback": 8 + }, + "readiness": true, + "comment": "Great QR onboarding and leaderboard polish.", + "tags": [ + "polish" + ] + }, + { + "projectId": "geostories", + "scores": { + "complexity": 7, + "creativity": 9, + "presentation": 8, + "feedback": 7 + }, + "readiness": false, + "comment": "Would like offline cache soon.", + "tags": [ + "mapping" + ] + } + ], + "popularRanking": [ + "pubky-live-vote", + "pubky-cookbook", + "pubky-mcp-server", + "geostories", + "monky", + "pubky-homeserver-cli", + "pubky-tag-extension", + "decentralized-wiki", + "booksy" + ] + }, + { + "voterId": "demo-voter-3", + "submittedAt": "2025-02-15T10:15:00.000Z", + "scores": [ + { + "projectId": "pubky-mcp-server", + "scores": { + "complexity": 8, + "creativity": 7, + "presentation": 8, + "feedback": 8 + }, + "readiness": true, + "comment": "Makes onboarding trivial.", + "tags": [ + "sdk" + ] + }, + { + "projectId": "pubky-homeserver-cli", + "scores": { + "complexity": 7, + "creativity": 6, + "presentation": 7, + "feedback": 7 + }, + "readiness": false, + "comment": "Add more validation checks.", + "tags": [ + "roadmap" + ] + }, + { + "projectId": "pubky-tag-extension", + "scores": { + "complexity": 6, + "creativity": 8, + "presentation": 7, + "feedback": 8 + }, + "readiness": false, + "comment": "Tag search is already helpful.", + "tags": [ + "browser" + ] + }, + { + "projectId": "booksy", + "scores": { + "complexity": 5, + "creativity": 7, + "presentation": 8, + "feedback": 7 + }, + "readiness": false, + "comment": "Sync roadmap makes sense.", + "tags": [ + "sharing" + ] + }, + { + "projectId": "pubky-cookbook", + "scores": { + "complexity": 6, + "creativity": 6, + "presentation": 9, + "feedback": 9 + }, + "readiness": true, + "comment": "Love the walkthrough format.", + "tags": [ + "docs" + ] + }, + { + "projectId": "decentralized-wiki", + "scores": { + "complexity": 7, + "creativity": 7, + "presentation": 6, + "feedback": 7 + }, + "readiness": false, + "comment": "Curious how moderation scales.", + "tags": [ + "wiki" + ] + }, + { + "projectId": "monky", + "scores": { + "complexity": 8, + "creativity": 8, + "presentation": 6, + "feedback": 6 + }, + "readiness": true, + "comment": "Key-based contracts feel fresh.", + "tags": [ + "identity" + ] + }, + { + "projectId": "pubky-live-vote", + "scores": { + "complexity": 8, + "creativity": 8, + "presentation": 9, + "feedback": 9 + }, + "readiness": true, + "comment": "Impressive offline fallback story.", + "tags": [ + "offline" + ] + }, + { + "projectId": "geostories", + "scores": { + "complexity": 7, + "creativity": 9, + "presentation": 8, + "feedback": 7 + }, + "readiness": false, + "comment": "Stories pop on the map.", + "tags": [ + "mapping" + ] + } + ], + "popularRanking": [ + "geostories", + "pubky-live-vote", + "pubky-mcp-server", + "pubky-cookbook", + "monky", + "pubky-tag-extension", + "pubky-homeserver-cli", + "decentralized-wiki", + "booksy" + ] + } +] diff --git a/pubky-live-vote/public/pubky-live-vote/summary.json b/pubky-live-vote/public/pubky-live-vote/summary.json new file mode 100644 index 0000000..f51be85 --- /dev/null +++ b/pubky-live-vote/public/pubky-live-vote/summary.json @@ -0,0 +1,123 @@ +{ + "generatedAt": "2025-02-15T12:00:00.000Z", + "totalVoters": 5, + "entries": [ + { + "projectId": "pubky-live-vote", + "total": 88.2, + "components": { + "complexity": 82, + "creativity": 88, + "readiness": 96, + "presentation": 92, + "feedback": 90, + "popular": 84, + "ai": 88 + } + }, + { + "projectId": "pubky-mcp-server", + "total": 82.5, + "components": { + "complexity": 78, + "creativity": 80, + "readiness": 88, + "presentation": 84, + "feedback": 82, + "popular": 80, + "ai": 86 + } + }, + { + "projectId": "pubky-homeserver-cli", + "total": 77.4, + "components": { + "complexity": 72, + "creativity": 74, + "readiness": 86, + "presentation": 80, + "feedback": 78, + "popular": 74, + "ai": 79 + } + }, + { + "projectId": "pubky-tag-extension", + "total": 74.1, + "components": { + "complexity": 70, + "creativity": 84, + "readiness": 68, + "presentation": 78, + "feedback": 80, + "popular": 70, + "ai": 75 + } + }, + { + "projectId": "booksy", + "total": 70.6, + "components": { + "complexity": 64, + "creativity": 78, + "readiness": 66, + "presentation": 80, + "feedback": 74, + "popular": 68, + "ai": 72 + } + }, + { + "projectId": "pubky-cookbook", + "total": 81.3, + "components": { + "complexity": 74, + "creativity": 76, + "readiness": 90, + "presentation": 88, + "feedback": 92, + "popular": 78, + "ai": 83 + } + }, + { + "projectId": "decentralized-wiki", + "total": 73.8, + "components": { + "complexity": 70, + "creativity": 74, + "readiness": 72, + "presentation": 72, + "feedback": 76, + "popular": 70, + "ai": 78 + } + }, + { + "projectId": "monky", + "total": 79.4, + "components": { + "complexity": 80, + "creativity": 82, + "readiness": 82, + "presentation": 70, + "feedback": 68, + "popular": 76, + "ai": 82 + } + }, + { + "projectId": "geostories", + "total": 85.2, + "components": { + "complexity": 78, + "creativity": 92, + "readiness": 80, + "presentation": 84, + "feedback": 82, + "popular": 82, + "ai": 84 + } + } + ] +} diff --git a/pubky-live-vote/src/App.tsx b/pubky-live-vote/src/App.tsx new file mode 100644 index 0000000..6352c94 --- /dev/null +++ b/pubky-live-vote/src/App.tsx @@ -0,0 +1,13 @@ +import { AuthProvider } from './context/AuthContext'; +import { ProjectProvider } from './context/ProjectContext'; +import { Layout } from './components/Layout'; + +const App = () => ( + + + + + +); + +export default App; diff --git a/pubky-live-vote/src/components/Layout.css b/pubky-live-vote/src/components/Layout.css new file mode 100644 index 0000000..4e117f6 --- /dev/null +++ b/pubky-live-vote/src/components/Layout.css @@ -0,0 +1,235 @@ +.app-shell { + display: flex; + flex-direction: column; + min-height: 100vh; + padding: clamp(1.25rem, 3vw, 2rem) clamp(1rem, 5vw, 3rem) clamp(2rem, 6vw, 3rem); + gap: clamp(1.25rem, 3vw, 2rem); + box-sizing: border-box; +} + +.top-nav { + display: grid; + grid-template-columns: auto minmax(200px, 1fr); + align-items: center; + gap: clamp(1rem, 3vw, 2rem); + padding: 0.9rem clamp(1rem, 2.5vw, 1.5rem); + background: linear-gradient(135deg, rgba(30, 34, 52, 0.92), rgba(17, 18, 30, 0.92)); + border-radius: 22px; + border: 1px solid rgba(120, 135, 255, 0.28); + box-shadow: 0 20px 40px rgba(5, 9, 20, 0.55); + position: sticky; + top: clamp(1.25rem, 3vw, 2rem); + z-index: 10; + backdrop-filter: blur(18px); +} + +.top-nav__brand { + display: flex; + align-items: center; + gap: 0.75rem; + font-weight: 700; + font-size: 1.1rem; + color: #f1f5ff; +} + +.top-nav__logo svg { + width: 36px; + height: 36px; +} + +.top-nav__brand-name { + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.top-nav__search { + position: relative; + display: flex; + align-items: center; +} + +.top-nav__search input { + width: 100%; + border-radius: 999px; + border: 1px solid rgba(126, 140, 255, 0.35); + background: radial-gradient(circle at top left, rgba(90, 105, 255, 0.25), rgba(20, 22, 35, 0.95) 55%); + color: #f1f5ff; + padding: 0.65rem 1.1rem; + font-size: 0.95rem; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); +} + +.top-nav__search input::placeholder { + color: rgba(203, 213, 255, 0.65); +} + +.top-nav__actions { + display: flex; + align-items: flex-end; + gap: 0.75rem; +} + +.top-nav__action { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; +} + +.top-nav__actions button { + width: 40px; + height: 40px; + border-radius: 14px; + border: 1px solid rgba(128, 141, 255, 0.35); + background: linear-gradient(145deg, rgba(23, 26, 43, 0.9), rgba(15, 16, 27, 0.95)); + color: rgba(206, 214, 255, 0.85); + display: grid; + place-items: center; + cursor: pointer; + transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; +} + +.top-nav__actions button:hover { + transform: translateY(-1px); + border-color: rgba(141, 157, 255, 0.6); + box-shadow: 0 12px 24px rgba(40, 45, 80, 0.45); +} + +.top-nav__action-label { + font-size: 0.7rem; + letter-spacing: 0.04em; + color: rgba(210, 220, 255, 0.75); + text-transform: uppercase; + opacity: 0; + transform: translateY(-6px); + transition: opacity 0.18s ease, transform 0.18s ease; + pointer-events: none; +} + +.top-nav__action:hover .top-nav__action-label, +.top-nav__action:focus-within .top-nav__action-label { + opacity: 1; + transform: translateY(0); +} + +.top-nav__actions svg { + width: 20px; + height: 20px; +} + +.top-nav__avatar { + font-weight: 700; + font-size: 1rem; + color: #ffdeff; + background: linear-gradient(140deg, rgba(255, 145, 255, 0.4), rgba(115, 102, 255, 0.55)); +} + +.app-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: clamp(1rem, 4vw, 2.5rem); + background: radial-gradient(circle at top left, rgba(141, 158, 255, 0.22), rgba(21, 22, 33, 0.92) 60%); + padding: clamp(1.25rem, 4vw, 2.5rem); + border-radius: 28px; + border: 1px solid rgba(137, 150, 255, 0.28); + box-shadow: 0 24px 50px rgba(8, 11, 25, 0.58); +} + +.app-header__copy { + max-width: 520px; +} + +.app-header h1 { + font-size: clamp(1.8rem, 3.2vw, 3rem); + margin: 0 0 0.5rem; + letter-spacing: -0.01em; +} + +.subtitle { + margin: 0; + color: rgba(210, 218, 255, 0.75); + font-size: 1rem; +} + +.app-main { + display: grid; + grid-template-columns: minmax(0, 360px) minmax(0, 1fr); + gap: clamp(1.2rem, 3vw, 2rem); + align-items: flex-start; +} + +.left-column, +.right-column { + display: flex; + flex-direction: column; + gap: clamp(1.1rem, 2.5vw, 1.75rem); +} + +.app-footer { + background: linear-gradient(135deg, rgba(22, 24, 37, 0.95), rgba(12, 13, 22, 0.98)); + border: 1px solid rgba(102, 116, 255, 0.25); + border-radius: 26px; + padding: clamp(1rem, 3vw, 1.75rem); + backdrop-filter: blur(18px); + box-shadow: 0 18px 40px rgba(6, 9, 22, 0.55); +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +@media (max-width: 1080px) { + .top-nav { + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; + } + + .top-nav__search { + grid-column: 1 / -1; + width: 100%; + } + + .top-nav__actions { + justify-self: end; + } + + .app-header { + flex-direction: column; + align-items: stretch; + } +} + +@media (max-width: 960px) { + .app-main { + grid-template-columns: 1fr; + } +} + +@media (max-width: 640px) { + .app-shell { + padding: 1rem; + gap: 1rem; + } + + .top-nav { + position: static; + border-radius: 18px; + } + + .app-header { + border-radius: 22px; + } + + .app-footer { + border-radius: 22px; + } +} diff --git a/pubky-live-vote/src/components/Layout.tsx b/pubky-live-vote/src/components/Layout.tsx new file mode 100644 index 0000000..1d1bcd3 --- /dev/null +++ b/pubky-live-vote/src/components/Layout.tsx @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import { useAuth } from '../context/AuthContext'; +import { useProjects } from '../context/ProjectContext'; +import { LoginCard } from './LoginCard'; +import { ProjectList } from './ProjectList'; +import { Leaderboard } from './Leaderboard'; +import { MyProjectPanel } from './MyProjectPanel'; +import { SubmissionBanner } from './SubmissionBanner'; +import './Layout.css'; + +export const Layout = () => { + const { session } = useAuth(); + const { submitBallot, hasPendingChanges, lastSubmittedAt } = useProjects(); + const [searchQuery, setSearchQuery] = useState(''); + + return ( +
+ +
+
+

Pubky Live Vote

+

Real-time scoring and tagging for the Pubky Hackathon

+
+ +
+ +
+
+ + {session && } +
+
+ +
+
+ +
+ +
+
+ ); +}; diff --git a/pubky-live-vote/src/components/Leaderboard.css b/pubky-live-vote/src/components/Leaderboard.css new file mode 100644 index 0000000..522cc08 --- /dev/null +++ b/pubky-live-vote/src/components/Leaderboard.css @@ -0,0 +1,91 @@ +.leaderboard { + display: grid; + gap: 1.15rem; +} + +.leaderboard header h2 { + margin: 0; +} + +.leaderboard__meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1rem; + align-items: center; + font-size: 0.9rem; + color: rgba(206, 214, 255, 0.78); + margin-top: 0.4rem; +} + +.leaderboard__source { + color: rgba(155, 178, 255, 0.85); +} + +.leaderboard__error { + margin: 0.6rem 0 0; + font-size: 0.9rem; + color: #ff8fa3; +} + +.leaderboard__table { + display: grid; + gap: 0.7rem; + overflow-x: auto; +} + +.leaderboard__row { + display: grid; + grid-template-columns: 60px minmax(160px, 1fr) repeat(6, minmax(60px, 1fr)); + gap: 0.8rem; + align-items: center; + background: linear-gradient(135deg, rgba(18, 20, 33, 0.9), rgba(12, 14, 24, 0.92)); + padding: 0.85rem 1.05rem; + border-radius: 16px; + border: 1px solid rgba(122, 136, 255, 0.32); + box-shadow: 0 16px 32px rgba(12, 16, 35, 0.45); +} + +.leaderboard__row:not(.leaderboard__row--header):hover { + border-color: rgba(155, 168, 255, 0.6); + box-shadow: 0 18px 38px rgba(24, 28, 60, 0.45); +} + +.leaderboard__row--header { + background: linear-gradient(135deg, rgba(29, 32, 52, 0.88), rgba(19, 21, 36, 0.92)); + border: 1px solid rgba(142, 156, 255, 0.45); + font-size: 0.82rem; + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 600; + color: rgba(198, 206, 255, 0.82); +} + +.leaderboard__row span[role='cell'] { + color: rgba(222, 229, 255, 0.85); +} + +.strong { + font-weight: 700; + color: #c9d7ff; +} + +@media (max-width: 960px) { + .leaderboard__row, + .leaderboard__row--header { + grid-template-columns: 50px minmax(140px, 1fr) repeat(6, minmax(50px, 1fr)); + } +} + +@media (max-width: 720px) { + .leaderboard__row, + .leaderboard__row--header { + grid-template-columns: 45px minmax(130px, 1fr) repeat(6, minmax(45px, 1fr)); + font-size: 0.85rem; + } +} + +@media (max-width: 640px) { + .leaderboard__table { + overflow-x: auto; + } +} diff --git a/pubky-live-vote/src/components/Leaderboard.tsx b/pubky-live-vote/src/components/Leaderboard.tsx new file mode 100644 index 0000000..0363c5e --- /dev/null +++ b/pubky-live-vote/src/components/Leaderboard.tsx @@ -0,0 +1,102 @@ +import { useMemo } from 'react'; +import { useProjects } from '../context/ProjectContext'; +import { useLiveLeaderboard } from '../hooks/useLiveLeaderboard'; +import type { LeaderboardEntry, Project } from '../types/project'; +import './Leaderboard.css'; + +const SOURCE_LABEL: Record<'remote-summary' | 'ballots' | 'local', string> = { + 'remote-summary': 'Pubky homeserver summary', + ballots: 'Aggregated ballots', + local: 'Local preview' +}; + +const formatUpdatedAt = (iso: string) => { + const parsed = Date.parse(iso); + if (Number.isNaN(parsed)) return 'unknown'; + const diffMs = Date.now() - parsed; + if (diffMs < 30_000) return 'just now'; + if (diffMs < 3_600_000) { + const minutes = Math.round(diffMs / 60_000); + return `${minutes} min ago`; + } + const hours = Math.round(diffMs / 3_600_000); + if (hours < 48) { + return `${hours} hr${hours === 1 ? '' : 's'} ago`; + } + return new Date(parsed).toLocaleString(); +}; + +const formatVoterCount = (count: number) => + `${count} voter${count === 1 ? '' : 's'}`; + +const numberFormatter = new Intl.NumberFormat(undefined, { + minimumFractionDigits: 0, + maximumFractionDigits: 1 +}); + +const integerFormatter = new Intl.NumberFormat(undefined, { + minimumFractionDigits: 0, + maximumFractionDigits: 0 +}); + +export const Leaderboard = () => { + const { projects } = useProjects(); + const { entries, totalVoters, generatedAt, source, isLoading, error } = useLiveLeaderboard(projects); + + const rows = useMemo(() => (entries.length ? entries : placeholderRows(projects)), [entries, projects]); + + return ( +
+
+

Live Leaderboard

+
+ {isLoading ? 'Loading scores…' : `${formatVoterCount(totalVoters)} · ${formatUpdatedAt(generatedAt)}`} + Source: {SOURCE_LABEL[source]} +
+ {error &&

{error.message}

} +
+
+
+ Rank + Project + Total + Complexity + Creativity + Readiness + Presentation + Feedback +
+ {rows.map((row, index) => ( +
+ {index + 1} + {row.projectName} + + {numberFormatter.format(row.total)} + + {integerFormatter.format(row.components.complexity)} + {integerFormatter.format(row.components.creativity)} + {integerFormatter.format(row.components.readiness)} + {integerFormatter.format(row.components.presentation)} + {integerFormatter.format(row.components.feedback)} +
+ ))} +
+
+ ); +}; + +const placeholderRows = (projects: Project[]): LeaderboardEntry[] => + projects.map((project) => ({ + projectId: project.id, + projectName: project.name, + total: 0, + components: { + complexity: 0, + creativity: 0, + readiness: 0, + presentation: 0, + feedback: 0, + popular: 0, + ai: project.aiScore ?? 0 + } + })); diff --git a/pubky-live-vote/src/components/LoginCard.tsx b/pubky-live-vote/src/components/LoginCard.tsx new file mode 100644 index 0000000..0b6c706 --- /dev/null +++ b/pubky-live-vote/src/components/LoginCard.tsx @@ -0,0 +1,93 @@ +import { useEffect, useMemo, useRef } from 'react'; +import QRCode from 'react-qr-code'; +import { useAuth } from '../context/AuthContext'; +import './Panel.css'; + +export const LoginCard = () => { + const { user, session, authMethod, isAuthenticating, authorizationUrl, connect, disconnect } = useAuth(); + const hasAttemptedAutoConnect = useRef(false); + + useEffect(() => { + if (session || isAuthenticating || hasAttemptedAutoConnect.current) { + return; + } + hasAttemptedAutoConnect.current = true; + void connect(); + }, [session, isAuthenticating, connect]); + + const statusMessage = useMemo(() => { + if (session) return ''; + if (authorizationUrl) { + return 'Scan the QR code with the Pubky app or open the link on this device.'; + } + if (isAuthenticating) { + return 'Preparing the Pubky auth flow…'; + } + if (hasAttemptedAutoConnect.current) { + return 'Unable to start the Pubky auth flow. Retry the connection to generate a new QR code.'; + } + return 'Connect to Pubky to start voting.'; + }, [session, isAuthenticating, authorizationUrl, authMethod]); + + const publicKey = useMemo(() => { + if (session) { + try { + return session.info.publicKey.z32(); + } catch (error) { + console.warn('Unable to read session public key', error); + } + } + return user?.publicKey ?? null; + }, [session, user]); + + return ( +
+
+
+

{session ? 'Connected to Pubky' : 'Connect to Pubky'}

+

+ {session + ? 'Your Pubky identity is active. You can vote, comment, and tag projects.' + : 'Authorize the app with Pubky to start voting. Scan the QR code or open the link below.'} +

+
+ {session && ( + + )} +
+ + {!session && ( +
+ {authorizationUrl ? ( +
+
+ +
+

{statusMessage}

+ + Open authorization link + +
+ ) : ( +
{statusMessage}
+ )} +
+ )} + + {session && ( +
+
+
Authentication
+
{authMethod ? 'Approved via the Pubky app' : 'Awaiting approval'}
+
+
+
Public Key
+
{publicKey ?? 'Unknown'}
+
+
+ )} +
+ ); +}; diff --git a/pubky-live-vote/src/components/MyProjectPanel.css b/pubky-live-vote/src/components/MyProjectPanel.css new file mode 100644 index 0000000..0187104 --- /dev/null +++ b/pubky-live-vote/src/components/MyProjectPanel.css @@ -0,0 +1,29 @@ +.my-project { + gap: 1.4rem; +} + +.my-project__body { + display: grid; + gap: 0.8rem; +} + +.my-project__label { + display: grid; + gap: 0.4rem; + font-size: 0.92rem; + color: rgba(206, 214, 255, 0.78); +} + +.my-project__label select { + border-radius: 14px; + border: 1px solid rgba(130, 145, 255, 0.38); + background: linear-gradient(135deg, rgba(17, 18, 30, 0.92), rgba(12, 14, 26, 0.94)); + color: rgba(223, 228, 255, 0.9); + padding: 0.55rem 0.8rem; +} + +.my-project__hint { + margin: 0; + font-size: 0.85rem; + color: rgba(198, 205, 255, 0.6); +} diff --git a/pubky-live-vote/src/components/MyProjectPanel.tsx b/pubky-live-vote/src/components/MyProjectPanel.tsx new file mode 100644 index 0000000..452b834 --- /dev/null +++ b/pubky-live-vote/src/components/MyProjectPanel.tsx @@ -0,0 +1,40 @@ +import { ChangeEvent } from 'react'; +import { useProjects } from '../context/ProjectContext'; +import './MyProjectPanel.css'; +import './Panel.css'; + +export const MyProjectPanel = () => { + const { projects, userProjectId, setUserProjectId } = useProjects(); + + const handleChange = (event: ChangeEvent) => { + const value = event.target.value; + setUserProjectId(value || null); + }; + + return ( +
+
+
+

My project

+

+ Select your team to disable self-voting and exclude your project from submissions. +

+
+
+
+ +

Only one project can be selected per voter.

+
+
+ ); +}; diff --git a/pubky-live-vote/src/components/Panel.css b/pubky-live-vote/src/components/Panel.css new file mode 100644 index 0000000..289c2b9 --- /dev/null +++ b/pubky-live-vote/src/components/Panel.css @@ -0,0 +1,146 @@ +.panel { + background: linear-gradient(160deg, rgba(30, 32, 51, 0.95), rgba(16, 17, 29, 0.94)); + border: 1px solid rgba(121, 135, 255, 0.35); + border-radius: 26px; + padding: clamp(1.35rem, 3vw, 1.9rem); + display: flex; + flex-direction: column; + gap: 1.35rem; + box-shadow: 0 24px 48px rgba(5, 8, 21, 0.6); + backdrop-filter: blur(20px); +} + +.panel__header { + display: flex; + flex-direction: column; + gap: 0.8rem; +} + +.panel__header h2 { + margin: 0; + font-size: clamp(1.1rem, 2vw, 1.55rem); +} + +.panel__subtitle { + margin: 0; + color: var(--muted); +} + +.button { + align-self: flex-start; + border-radius: 14px; + padding: 0.6rem 1.25rem; + border: 1px solid rgba(155, 168, 255, 0.55); + background: linear-gradient(135deg, rgba(145, 158, 255, 0.95), rgba(96, 108, 254, 0.9)); + color: #050611; + font-weight: 600; + cursor: pointer; + transition: transform 0.18s ease, box-shadow 0.18s ease, filter 0.18s ease; +} + +.button:hover { + transform: translateY(-1px); + box-shadow: 0 14px 28px rgba(87, 105, 255, 0.35); + filter: brightness(1.05); +} + +.button:disabled { + cursor: not-allowed; + opacity: 0.5; + transform: none; + box-shadow: none; +} + +.button--ghost { + background: linear-gradient(135deg, rgba(21, 22, 35, 0.95), rgba(16, 17, 30, 0.92)); + color: rgba(210, 216, 255, 0.9); + border: 1px solid rgba(131, 145, 255, 0.35); + box-shadow: none; +} + +.button--ghost:hover { + background: linear-gradient(135deg, rgba(26, 28, 46, 0.95), rgba(18, 19, 34, 0.96)); + box-shadow: 0 10px 18px rgba(40, 47, 82, 0.4); +} + +.qr-wrapper { + display: flex; + justify-content: center; + align-items: center; + min-height: 220px; + background: linear-gradient(135deg, rgba(17, 20, 36, 0.85), rgba(12, 15, 28, 0.9)); + border-radius: 18px; + border: 1px dashed rgba(138, 152, 255, 0.35); +} + +.qr-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.75rem; + text-align: center; +} + +.qr-code { + background: rgba(222, 225, 236, 0.92); + padding: clamp(0.85rem, 2.5vw, 1.1rem); + border-radius: 16px; + box-shadow: 0 20px 40px rgba(7, 9, 21, 0.45); + margin-top: clamp(0.35rem, 1.2vw, 0.75rem); +} + +.qr-code svg { + width: min(220px, 60vw); + height: auto; + display: block; +} + +.qr-caption { + margin: 0; + color: rgba(209, 216, 255, 0.75); + max-width: 18rem; +} + +.qr-link { + color: rgba(140, 152, 255, 0.95); + text-decoration: none; + font-weight: 600; + transition: color 0.18s ease; +} + +.qr-link:hover { + color: rgba(180, 190, 255, 1); +} + +.qr-placeholder { + color: rgba(209, 216, 255, 0.65); +} + +.identity-list { + display: grid; + gap: 0.75rem; + margin: 0; +} + +.identity-list dt { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: rgba(166, 175, 255, 0.75); +} + +.identity-list dd { + margin: 0.2rem 0 0; + font-weight: 600; +} + +.panel__actions { + display: flex; + justify-content: flex-start; +} + +.mono { + font-family: 'JetBrains Mono', 'Fira Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, 'Courier New', monospace; + font-size: 0.82rem; + word-break: break-all; +} diff --git a/pubky-live-vote/src/components/ProjectCard.css b/pubky-live-vote/src/components/ProjectCard.css new file mode 100644 index 0000000..3a29c57 --- /dev/null +++ b/pubky-live-vote/src/components/ProjectCard.css @@ -0,0 +1,191 @@ +.project-card { + background: linear-gradient(150deg, rgba(23, 25, 42, 0.95), rgba(12, 13, 24, 0.92)); + border-radius: 24px; + border: 1px solid rgba(118, 132, 255, 0.32); + padding: clamp(1.2rem, 2.4vw, 1.6rem); + display: flex; + flex-direction: column; + gap: 1.35rem; + transition: transform 0.2s ease, border 0.2s ease, box-shadow 0.2s ease; + position: relative; + overflow: hidden; +} + +.project-card--own { + border-color: rgba(118, 132, 255, 0.22); + box-shadow: inset 0 0 0 1px rgba(97, 110, 184, 0.28); +} + +.project-card--own:hover { + transform: none; + border-color: rgba(118, 132, 255, 0.22); + box-shadow: inset 0 0 0 1px rgba(97, 110, 184, 0.28); +} + +.project-card::before { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(circle at top right, rgba(120, 140, 255, 0.18), transparent 55%); + opacity: 0; + transition: opacity 0.2s ease; + pointer-events: none; +} + +.project-card:hover { + transform: translateY(-4px); + border-color: rgba(153, 168, 255, 0.55); + box-shadow: 0 18px 40px rgba(28, 33, 72, 0.45); +} + +.project-card:hover::before { + opacity: 1; +} + +.project-card__header { + display: flex; + flex-direction: column; + gap: 0.85rem; +} + +.project-card__header h3 { + margin: 0; + font-size: clamp(1.15rem, 2.4vw, 1.45rem); + letter-spacing: -0.01em; +} + +.project-card__description { + margin: 0; + color: rgba(204, 212, 255, 0.78); + line-height: 1.6; +} + +.project-card__tags { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; +} + +.project-card__team { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.55rem; +} + +.project-card__team-label { + font-size: 0.68rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.14em; + color: rgba(255, 216, 166, 0.85); +} + +.project-card__team-tags { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; +} + +.tag { + padding: 0.35rem 0.75rem; + border-radius: 999px; + font-size: 0.78rem; + font-weight: 600; + text-transform: lowercase; + letter-spacing: 0.02em; + background: var(--tag-blue); + color: #0a0c17; + box-shadow: 0 6px 14px rgba(115, 130, 255, 0.45); + border: 1px solid rgba(255, 255, 255, 0.15); +} + +.tag.tone-green { + background: var(--tag-green); + box-shadow: 0 6px 14px rgba(54, 215, 167, 0.38); +} + +.tag.tone-purple { + background: var(--tag-purple); + box-shadow: 0 6px 14px rgba(215, 154, 255, 0.42); +} + +.tag--team { + background: var(--tag-team); + box-shadow: 0 6px 14px rgba(255, 186, 121, 0.38); + color: #2a1705; + border-color: rgba(255, 241, 220, 0.45); +} + +.project-card__scores { + display: grid; + gap: 1.1rem; +} + +.project-card__meta { + display: grid; + gap: 1.05rem; +} + +.checkbox { + display: flex; + align-items: center; + gap: 0.65rem; + font-weight: 500; + color: rgba(216, 221, 255, 0.85); +} + +.checkbox input { + width: 22px; + height: 22px; + border-radius: 7px; + border: 1px solid rgba(134, 147, 255, 0.45); + accent-color: var(--accent-strong); + background-color: rgba(14, 17, 31, 0.9); +} + +.checkbox input:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.project-card__comment { + display: grid; + gap: 0.45rem; +} + +.project-card__comment label { + font-weight: 600; + font-size: 0.92rem; + color: rgba(204, 210, 255, 0.85); +} + +.project-card__comment textarea { + resize: vertical; + min-height: 96px; + border-radius: 14px; + border: 1px solid rgba(131, 144, 255, 0.32); + padding: 0.8rem 0.85rem; + background: linear-gradient(135deg, rgba(18, 20, 34, 0.92), rgba(14, 16, 28, 0.94)); + color: rgba(223, 228, 255, 0.92); + transition: border 0.18s ease, box-shadow 0.18s ease; +} + +.project-card__comment textarea:focus { + outline: none; + border-color: rgba(155, 168, 255, 0.65); + box-shadow: 0 0 0 3px rgba(121, 136, 255, 0.25); +} + +.project-card__comment textarea:disabled { + cursor: not-allowed; + background: linear-gradient(135deg, rgba(18, 20, 34, 0.68), rgba(14, 16, 28, 0.7)); + border-color: rgba(131, 144, 255, 0.22); +} + +.project-card__own-hint { + margin: 0; + margin-top: -0.4rem; + font-size: 0.85rem; + color: rgba(198, 205, 255, 0.65); +} diff --git a/pubky-live-vote/src/components/ProjectCard.tsx b/pubky-live-vote/src/components/ProjectCard.tsx new file mode 100644 index 0000000..2ccd366 --- /dev/null +++ b/pubky-live-vote/src/components/ProjectCard.tsx @@ -0,0 +1,128 @@ +import { ChangeEvent } from 'react'; +import type { Project, ScoreComponent } from '../types/project'; +import { getTagTone } from '../utils/tagTone'; +import { ScoreSlider } from './ScoreSlider'; +import { TagInput } from './TagInput'; +import './ProjectCard.css'; + +interface Props { + project: Project; + onScoreChange: (projectId: string, component: ScoreComponent, value: number) => void; + onReadinessToggle: (projectId: string, ready: boolean) => void; + onCommentChange: (projectId: string, comment: string) => void; + onTagsChange: (projectId: string, tags: string[]) => void; + isOwnProject?: boolean; +} + +const SCORE_LABELS: Record = { + complexity: 'Complexity', + creativity: 'Creativity / Practicality', + presentation: 'Team Presentation', + feedback: 'Feedback Quality' +}; + +export const ProjectCard = ({ + project, + onScoreChange, + onReadinessToggle, + onCommentChange, + onTagsChange, + isOwnProject = false +}: Props) => { + const handleSlider = (component: ScoreComponent) => (value: number) => { + if (isOwnProject) return; + onScoreChange(project.id, component, value); + }; + + const handleComment = (event: ChangeEvent) => { + if (isOwnProject) return; + onCommentChange(project.id, event.target.value); + }; + + const handleReadiness = (event: ChangeEvent) => { + if (isOwnProject) return; + onReadinessToggle(project.id, event.target.checked); + }; + + const handleTagsUpdate = (tags: string[]) => { + if (isOwnProject) return; + onTagsChange(project.id, tags); + }; + + return ( +
+
+
+

{project.name}

+

{project.description}

+
+
+ {project.tags.map((tag) => ( + + #{tag} + + ))} +
+ {project.teamMembers?.length ? ( +
+ Team +
+ {project.teamMembers.map((member) => ( + + {member.startsWith('@') ? member : `@${member}`} + + ))} +
+
+ ) : null} +
+ +
+ {Object.entries(SCORE_LABELS).map(([component, label]) => ( + + ))} +
+ +
+ +
+ +