diff options
author | polwex <polwex@sortug.com> | 2025-09-11 01:48:14 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-09-11 01:48:14 +0700 |
commit | b1d68ac307ed87d63e83820cbdf843fff0fd9f7f (patch) | |
tree | d6a684a70a80509e68ff667b842aa4e4c091906f /front |
init
Diffstat (limited to 'front')
99 files changed, 10124 insertions, 0 deletions
diff --git a/front/.gitignore b/front/.gitignore new file mode 100644 index 0000000..247c9a3 --- /dev/null +++ b/front/.gitignore @@ -0,0 +1,34 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/front/README.md b/front/README.md new file mode 100644 index 0000000..7959ce4 --- /dev/null +++ b/front/README.md @@ -0,0 +1,69 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + ...tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + ...tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + ...tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/front/bun.lock b/front/bun.lock new file mode 100644 index 0000000..acffc6f --- /dev/null +++ b/front/bun.lock @@ -0,0 +1,556 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "front", + "dependencies": { + "@tanstack/react-query": "^5.85.9", + "any-ascii": "^0.3.3", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-hot-toast": "^2.6.0", + "urbit-api": "file:../../../urbit/bun/http-api", + "urbit-ob": "file:../../../urbit/bun/urbit-ob", + "urbit-sigils": "file:../../../urbit/bun/sigil-ts", + "wouter": "^3.7.1", + "zustand": "^5.0.8", + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react": "^5.0.0", + "eslint": "^9.33.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.39.1", + "vite": "^7.1.2", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], + + "@babel/core": ["@babel/core@7.28.3", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@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.3", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ=="], + + "@babel/generator": ["@babel/generator@7.28.3", "", { "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" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "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" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.2" } }, "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw=="], + + "@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], + + "@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.8.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.3.1", "", {}, "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA=="], + + "@eslint/core": ["@eslint/core@0.15.2", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.34.0", "", {}, "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.5", "", { "dependencies": { "@eslint/core": "^0.15.2", "levn": "^0.4.1" } }, "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.34", "", {}, "sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.0", "", { "os": "android", "cpu": "arm" }, "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.0", "", { "os": "android", "cpu": "arm64" }, "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.50.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.50.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.50.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.50.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.50.0", "", { "os": "linux", "cpu": "arm" }, "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.50.0", "", { "os": "linux", "cpu": "arm" }, "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.50.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.50.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.50.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.50.0", "", { "os": "linux", "cpu": "none" }, "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.50.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.50.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.50.0", "", { "os": "linux", "cpu": "x64" }, "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.50.0", "", { "os": "none", "cpu": "arm64" }, "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.50.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.50.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.85.9", "", {}, "sha512-5fxb9vwyftYE6KFLhhhDyLr8NO75+Wpu7pmTo+TkwKmMX2oxZDoLwcqGP8ItKSpUMwk3urWgQDZfyWr5Jm9LsQ=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.85.9", "", { "dependencies": { "@tanstack/query-core": "5.85.9" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-2T5zgSpcOZXGkH/UObIbIkGmUPQqZqn7esVQFXLOze622h4spgWf5jmvrqAo9dnI13/hyMcNsF1jsoDcb59nJQ=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/invariant": ["@types/invariant@2.2.37", "", {}, "sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="], + + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + + "@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.42.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/type-utils": "8.42.0", "@typescript-eslint/utils": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.42.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.42.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.42.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.42.0", "@typescript-eslint/types": "^8.42.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0" } }, "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.42.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/utils": "8.42.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.42.0", "", {}, "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.42.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.42.0", "@typescript-eslint/tsconfig-utils": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.42.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.0.2", "", { "dependencies": { "@babel/core": "^7.28.3", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.34", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-tmyFgixPZCx2+e6VO9TNITWcCQl8+Nl/E8YbAyPVv85QCc7/A3JrdfG2A8gIzvVhWuzMOVrFW1aReaNxrI6tbw=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "any-ascii": ["any-ascii@0.3.3", "", {}, "sha512-8hm+zPrc1VnlxD5eRgMo9F9k2wEMZhbZVLKwA/sPKIt6ywuz7bI9uV/yb27uvc8fv8q6Wl2piJT51q1saKX0Jw=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.25.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg=="], + + "bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001739", "", {}, "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deep-rename-keys": ["deep-rename-keys@0.2.1", "", { "dependencies": { "kind-of": "^3.0.2", "rename-keys": "^1.1.2" } }, "sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.213", "", {}, "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q=="], + + "esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.34.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.34.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], + + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.20", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "eventemitter3": ["eventemitter3@2.0.3", "", {}, "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="], + + "goober": ["goober@2.1.16", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], + + "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="], + + "react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="], + + "react-hot-toast": ["react-hot-toast@2.6.0", "", { "dependencies": { "csstype": "^3.1.3", "goober": "^2.1.16" }, "peerDependencies": { "react": ">=16", "react-dom": ">=16" } }, "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg=="], + + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + + "regexparam": ["regexparam@3.0.0", "", {}, "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q=="], + + "rename-keys": ["rename-keys@1.2.0", "", {}, "sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.50.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.0", "@rollup/rollup-android-arm64": "4.50.0", "@rollup/rollup-darwin-arm64": "4.50.0", "@rollup/rollup-darwin-x64": "4.50.0", "@rollup/rollup-freebsd-arm64": "4.50.0", "@rollup/rollup-freebsd-x64": "4.50.0", "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", "@rollup/rollup-linux-arm-musleabihf": "4.50.0", "@rollup/rollup-linux-arm64-gnu": "4.50.0", "@rollup/rollup-linux-arm64-musl": "4.50.0", "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", "@rollup/rollup-linux-ppc64-gnu": "4.50.0", "@rollup/rollup-linux-riscv64-gnu": "4.50.0", "@rollup/rollup-linux-riscv64-musl": "4.50.0", "@rollup/rollup-linux-s390x-gnu": "4.50.0", "@rollup/rollup-linux-x64-gnu": "4.50.0", "@rollup/rollup-linux-x64-musl": "4.50.0", "@rollup/rollup-openharmony-arm64": "4.50.0", "@rollup/rollup-win32-arm64-msvc": "4.50.0", "@rollup/rollup-win32-ia32-msvc": "4.50.0", "@rollup/rollup-win32-x64-msvc": "4.50.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "svgson": ["svgson@5.3.1", "", { "dependencies": { "deep-rename-keys": "^0.2.1", "xml-reader": "2.4.3" } }, "sha512-qdPgvUNWb40gWktBJnbJRelWcPzkLed/ShhnRsjbayXz8OtdPOzbil9jtiZdrYvSDumAz/VNQr6JaNfPx/gvPA=="], + + "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "transformation-matrix": ["transformation-matrix@3.1.0", "", {}, "sha512-oYubRWTi2tYFHAL2J8DLvPIqIYcYZ0fSOi2vmSy042Ho4jBW2ce6VP7QfD44t65WQz6bw5w1Pk22J7lcUpaTKA=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "typescript-eslint": ["typescript-eslint@8.42.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.42.0", "@typescript-eslint/parser": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/utils": "8.42.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg=="], + + "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "urbit-api": ["@urbit/http-api@file:../../../urbit/bun/http-api", { "devDependencies": { "@types/bun": "latest", "typescript": "^5" } }], + + "urbit-ob": ["urbit-ob-ts@file:../../../urbit/bun/urbit-ob", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], + + "urbit-sigils": ["sigil-ts@file:../../../urbit/bun/sigil-ts", { "dependencies": { "invariant": "^2.2.4", "react": "^19.1.0", "svgson": "^5.3.1", "transformation-matrix": "^3.0.0" }, "devDependencies": { "@types/bun": "latest", "@types/invariant": "^2.2.37", "@types/react": "^19.1.2" }, "peerDependencies": { "typescript": "^5" } }], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], + + "vite": ["vite@7.1.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wouter": ["wouter@3.7.1", "", { "dependencies": { "mitt": "^3.0.1", "regexparam": "^3.0.0", "use-sync-external-store": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-od5LGmndSUzntZkE2R5CHhoiJ7YMuTIbiXsa0Anytc2RATekgv4sfWRAxLEULBrp7ADzinWQw8g470lkT8+fOw=="], + + "xml-lexer": ["xml-lexer@0.2.2", "", { "dependencies": { "eventemitter3": "^2.0.0" } }, "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w=="], + + "xml-reader": ["xml-reader@2.4.3", "", { "dependencies": { "eventemitter3": "^2.0.0", "xml-lexer": "^0.2.2" } }, "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + } +} diff --git a/front/eslint.config.js b/front/eslint.config.js new file mode 100644 index 0000000..d94e7de --- /dev/null +++ b/front/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { globalIgnores } from 'eslint/config' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/front/index.html b/front/index.html new file mode 100644 index 0000000..94cc361 --- /dev/null +++ b/front/index.html @@ -0,0 +1,16 @@ +<!doctype html> +<html lang="en"> + +<head> + <meta charset="UTF-8" /> + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Nostrill</title> +</head> + +<body> + <div id="root"></div> + <script type="module" src="/src/main.tsx"></script> +</body> + +</html>
\ No newline at end of file diff --git a/front/package.json b/front/package.json new file mode 100644 index 0000000..35395a9 --- /dev/null +++ b/front/package.json @@ -0,0 +1,37 @@ +{ + "name": "front", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.85.9", + "any-ascii": "^0.3.3", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-hot-toast": "^2.6.0", + "urbit-api": "file:../../../urbit/bun/http-api", + "urbit-ob": "file:../../../urbit/bun/urbit-ob", + "urbit-sigils": "file:../../../urbit/bun/sigil-ts", + "wouter": "^3.7.1", + "zustand": "^5.0.8" + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react": "^5.0.0", + "eslint": "^9.33.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.39.1", + "vite": "^7.1.2" + } +} diff --git a/front/public/favicon.svg b/front/public/favicon.svg new file mode 100644 index 0000000..790198c --- /dev/null +++ b/front/public/favicon.svg @@ -0,0 +1,22 @@ +<svg +xmlns="http://www.w3.org/2000/svg" +xmlns:xlink="http://www.w3.org/1999/xlink" +aria-hidden="true" +role="img" +class="iconify iconify--fa-solid" +width="40" +height="32" +preserveAspectRatio="xMidYMid meet" +viewBox="0 0 640 512"> + <style> + path { + fill: #000; + } + @media (prefers-color-scheme: dark) { + path { + fill: #fff; + } + } +</style> + +<path d="M544 32h-16.36C513.04 12.68 490.09 0 464 0c-44.18 0-80 35.82-80 80v20.98L12.09 393.57A30.216 30.216 0 0 0 0 417.74c0 22.46 23.64 37.07 43.73 27.03L165.27 384h96.49l44.41 120.1c2.27 6.23 9.15 9.44 15.38 7.17l22.55-8.21c6.23-2.27 9.44-9.15 7.17-15.38L312.94 384H352c1.91 0 3.76-.23 5.66-.29l44.51 120.38c2.27 6.23 9.15 9.44 15.38 7.17l22.55-8.21c6.23-2.27 9.44-9.15 7.17-15.38l-41.24-111.53C485.74 352.8 544 279.26 544 192v-80l96-16c0-35.35-42.98-64-96-64zm-80 72c-13.25 0-24-10.75-24-24c0-13.26 10.75-24 24-24s24 10.74 24 24c0 13.25-10.75 24-24 24z" fill="currentColor"></path></svg> diff --git a/front/public/fonts/Inter b/front/public/fonts/Inter new file mode 120000 index 0000000..7a1c26c --- /dev/null +++ b/front/public/fonts/Inter @@ -0,0 +1 @@ +/home/y/code/fonts/Inter
\ No newline at end of file diff --git a/front/public/fonts/Source_Code_Pro b/front/public/fonts/Source_Code_Pro new file mode 120000 index 0000000..ab04caf --- /dev/null +++ b/front/public/fonts/Source_Code_Pro @@ -0,0 +1 @@ +/home/y/code/fonts/Source_Code_Pro
\ No newline at end of file diff --git a/front/public/nostril-icon.png b/front/public/nostril-icon.png Binary files differnew file mode 100644 index 0000000..73be722 --- /dev/null +++ b/front/public/nostril-icon.png diff --git a/front/src/App.tsx b/front/src/App.tsx new file mode 100644 index 0000000..60ca66a --- /dev/null +++ b/front/src/App.tsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; +import useLocalState from "@/state/state"; +import Router from "./Router"; +import "@/styles/styles.css"; +import { ThemeProvider } from "@/styles/ThemeProvider"; +import spinner from "@/assets/crowspinner.gif"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Toaster } from "react-hot-toast"; +const queryClient = new QueryClient(); + +// const isMobile = MOBILE_BROWSER_REGEX.test(navigator.userAgent); + +function App() { + const [loading, setLoading] = useState(true); + console.log("NOSTRIL INIT"); + const { init, modal } = useLocalState(); + useEffect(() => { + init().then((_res: any) => { + setLoading(false); + }); + }, []); + if (loading) + return ( + <div className="global-center"> + <img id="global-spinner" src={spinner} alt="" /> + <h3 style={{ textAlign: "center" }}>Syncing with your Urbit...</h3> + </div> + ); + else + return ( + <ThemeProvider> + <QueryClientProvider client={queryClient}> + {/* {isMobile ? <MobileUI /> : <DesktopUI />} */} + <Router /> + {modal && modal} + <Toaster position="top-center" /> + </QueryClientProvider> + </ThemeProvider> + ); +} + +export default App; diff --git a/front/src/Router.tsx b/front/src/Router.tsx new file mode 100644 index 0000000..b7b033e --- /dev/null +++ b/front/src/Router.tsx @@ -0,0 +1,29 @@ +import Sidebar from "@/components/layout/Sidebar"; + +// new +import Feed from "@/pages/Feed"; +import Settings from "@/pages/Settings"; +import { Switch, Router, Redirect, Route } from "wouter"; + +export default function r() { + return ( + <Switch> + <Router base="/apps/nostril"> + <Sidebar /> + <main> + <Route path="/" component={toGlobal} /> + <Route path="/sets" component={Settings} /> + <Route path="/feed/:taip" component={Feed} /> + </main> + </Router> + <Route component={P404} /> + </Switch> + ); +} +function toGlobal() { + return <Redirect to="/feed/nostr" />; +} + +export function P404() { + return <h1 className="x-center">404</h1>; +} diff --git a/front/src/assets/crowspinner.gif b/front/src/assets/crowspinner.gif Binary files differnew file mode 100644 index 0000000..d0033d3 --- /dev/null +++ b/front/src/assets/crowspinner.gif diff --git a/front/src/assets/icons/bell.svg b/front/src/assets/icons/bell.svg new file mode 100644 index 0000000..98e88cd --- /dev/null +++ b/front/src/assets/icons/bell.svg @@ -0,0 +1,3 @@ +<svg width="18" height="21" viewBox="0 0 18 21" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M5.96822 18.0949C6.16344 18.7472 6.56388 19.3192 7.11007 19.7259C7.65625 20.1326 8.31904 20.3522 9 20.3522C9.68095 20.3522 10.3437 20.1326 10.8899 19.7259C11.4361 19.3192 11.8365 18.7472 12.0318 18.0949H5.96822ZM0.867065 17.1912H17.1329V14.4802L15.3256 11.7693V7.25097C15.3256 6.42027 15.162 5.59772 14.8441 4.83026C14.5262 4.0628 14.0603 3.36547 13.4729 2.77808C12.8855 2.1907 12.1882 1.72475 11.4207 1.40686C10.6532 1.08897 9.83069 0.925354 9 0.925354C8.1693 0.925354 7.34675 1.08897 6.57929 1.40686C5.81183 1.72475 5.1145 2.1907 4.52711 2.77808C3.93972 3.36547 3.47378 4.0628 3.15589 4.83026C2.838 5.59772 2.67438 6.42027 2.67438 7.25097V11.7693L0.867065 14.4802V17.1912Z" fill="#111111"/> +</svg> diff --git a/front/src/assets/icons/comet.svg b/front/src/assets/icons/comet.svg new file mode 100644 index 0000000..2d5c3f5 --- /dev/null +++ b/front/src/assets/icons/comet.svg @@ -0,0 +1,23 @@ +<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" style="width: 256px; height: 256px; opacity: 1;" xml:space="preserve"> +<g> + <path class="st0" d="M503.694,7.871c1.344-2.047,1.047-4.781-0.734-6.453c-1.781-1.703-4.5-1.891-6.484-0.438l-98.547,71.344 + c-1.891,1.359-4.484,1.266-6.266-0.25s-2.313-4.047-1.266-6.141l10.844-21.656c1.031-2.047,0.547-4.547-1.156-6.063 + c-1.719-1.531-4.25-1.719-6.172-0.469L83.006,230.137c-3.156,1.813-6.297,3.688-9.328,5.75l-0.25,0.156l0.016,0.016 + c-7.625,5.219-14.922,11.125-21.688,17.891c-59.047,59.031-59.047,154.75-0.016,213.766 + c29.531,29.516,68.219,44.281,106.906,44.281s77.359-14.766,106.891-44.281c6.766-6.766,12.656-14.047,17.875-21.672v0.016 + l0.109-0.172c1.844-2.703,3.516-5.484,5.172-8.281l188.375-302.078c1.203-1.844,1.094-4.25-0.281-5.953 + c-1.375-1.734-3.688-2.375-5.75-1.594l-46.829,17.563c-2.063,0.75-4.375,0.125-5.75-1.594s-1.484-4.125-0.266-5.953L503.694,7.871z + M158.647,464.73c-27.766,0-53.859-10.797-73.484-30.438c-40.5-40.5-40.5-106.406,0-146.922 + c6.813-6.797,14.422-12.469,22.578-17.063c7.406-4.172,15.297-7.391,23.5-9.641c0.766-0.203,1.547-0.375,2.344-0.578 + c3.172-0.797,6.406-1.422,9.672-1.906c1.031-0.156,2.047-0.328,3.078-0.453c4.047-0.484,8.156-0.797,12.313-0.797 + c27.75,0,53.828,10.813,73.453,30.438c2.344,2.328,4.516,4.781,6.578,7.281c0.688,0.813,1.297,1.672,1.938,2.5 + c1.344,1.734,2.641,3.469,3.859,5.25c0.703,1.031,1.359,2.063,2.016,3.109c1.047,1.656,2.047,3.344,3,5.063 + c0.609,1.109,1.219,2.234,1.797,3.359c0.859,1.719,1.656,3.484,2.422,5.234c0.5,1.141,1.031,2.266,1.484,3.422 + c0.813,2.063,1.516,4.172,2.188,6.266c0.656,2.031,1.219,4.063,1.75,6.125c0.391,1.563,0.813,3.109,1.141,4.688 + c0.344,1.703,0.609,3.438,0.875,5.156c0.188,1.219,0.375,2.438,0.516,3.656c0.219,1.875,0.391,3.75,0.5,5.625 + c0.063,1.016,0.109,2.047,0.156,3.063c0.063,2.047,0.109,4.094,0.047,6.141c-0.016,0.656-0.063,1.328-0.094,1.984 + c-0.453,10.406-2.438,20.766-6,30.688c-0.063,0.219-0.141,0.438-0.219,0.641c-0.891,2.422-1.875,4.813-2.938,7.188 + c-0.125,0.266-0.266,0.563-0.391,0.828c-1.125,2.406-2.313,4.781-3.609,7.094c-4.547,8.078-10.141,15.703-17,22.563 + C212.475,453.934,186.397,464.73,158.647,464.73z"></path> +</g> +</svg> diff --git a/front/src/assets/icons/copy.svg b/front/src/assets/icons/copy.svg new file mode 100644 index 0000000..714e9f5 --- /dev/null +++ b/front/src/assets/icons/copy.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="6.5" y="6.5" width="9" height="13" rx="1.5" stroke="#000000"/>
+<path d="M8.5 6C8.5 5.17157 9.17157 4.5 10 4.5H16C16.8284 4.5 17.5 5.17157 17.5 6V16C17.5 16.8284 16.8284 17.5 16 17.5" stroke="#000000"/>
+</svg>
\ No newline at end of file diff --git a/front/src/assets/icons/crow.svg b/front/src/assets/icons/crow.svg new file mode 100644 index 0000000..e967970 --- /dev/null +++ b/front/src/assets/icons/crow.svg @@ -0,0 +1,29 @@ +<svg +xmlns="http://www.w3.org/2000/svg" +xmlns:xlink="http://www.w3.org/1999/xlink" +aria-hidden="true" +role="img" +class="iconify iconify--fa-solid" +width="40" +height="32" +preserveAspectRatio="xMidYMid meet" +viewBox="0 0 640 512"> + <style> + path { + fill: #000; + } + @media (prefers-color-scheme: dark) { + path { + <!-- fill: #fff; --> + fill: linear-gradient( + 90deg, + rgba(168, 221, 228, 0.2) 0%, + rgba(150, 221, 233, 0.2) 52%, + rgba(0, 209, 255, 0.2) 100% + ); + } + } +</style> + +<path d="M544 32h-16.36C513.04 12.68 490.09 0 464 0c-44.18 0-80 35.82-80 80v20.98L12.09 393.57A30.216 30.216 0 0 0 0 417.74c0 22.46 23.64 37.07 43.73 27.03L165.27 384h96.49l44.41 120.1c2.27 6.23 9.15 9.44 15.38 7.17l22.55-8.21c6.23-2.27 9.44-9.15 7.17-15.38L312.94 384H352c1.91 0 3.76-.23 5.66-.29l44.51 120.38c2.27 6.23 9.15 9.44 15.38 7.17l22.55-8.21c6.23-2.27 9.44-9.15 7.17-15.38l-41.24-111.53C485.74 352.8 544 279.26 544 192v-80l96-16c0-35.35-42.98-64-96-64zm-80 72c-13.25 0-24-10.75-24-24c0-13.26 10.75-24 24-24s24 10.74 24 24c0 13.25-10.75 24-24 24z" fill="currentColor"></path></svg> + diff --git a/front/src/assets/icons/emoji.svg b/front/src/assets/icons/emoji.svg new file mode 100644 index 0000000..7a957fd --- /dev/null +++ b/front/src/assets/icons/emoji.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg width="800px" height="800px" viewBox="-3 -3 24 24"
+ xmlns="http://www.w3.org/2000/svg">
+ <path fill="#000000" fill-rule="evenodd" d="M255,160 L256,160 C256,162.209139 254.209139,164 252,164 C249.790861,164 248,162.209139 248,160 L249,160 C249,161.656854 250.343146,163 252,163 C253.656854,163 255,161.656854 255,160 Z M252,168 C256.970563,168 261,163.970563 261,159 C261,154.029437 256.970563,150 252,150 C247.029437,150 243,154.029437 243,159 C243,163.970563 247.029437,168 252,168 Z M252,167 C256.418278,167 260,163.418278 260,159 C260,154.581722 256.418278,151 252,151 C247.581722,151 244,154.581722 244,159 C244,163.418278 247.581722,167 252,167 Z M249,158 C249.552285,158 250,157.552285 250,157 C250,156.447715 249.552285,156 249,156 C248.447715,156 248,156.447715 248,157 C248,157.552285 248.447715,158 249,158 Z M255,158 C255.552285,158 256,157.552285 256,157 C256,156.447715 255.552285,156 255,156 C254.447715,156 254,156.447715 254,157 C254,157.552285 254.447715,158 255,158 Z" transform="translate(-243 -150)"/>
+</svg>
\ No newline at end of file diff --git a/front/src/assets/icons/home.svg b/front/src/assets/icons/home.svg new file mode 100644 index 0000000..64d7984 --- /dev/null +++ b/front/src/assets/icons/home.svg @@ -0,0 +1,3 @@ +<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M19.743 10.9698L10.743 0.96979C10.364 0.54779 9.63599 0.54779 9.25699 0.96979L0.256994 10.9698C0.127674 11.1135 0.0427905 11.2916 0.0126187 11.4826C-0.017553 11.6736 0.00828102 11.8692 0.0869934 12.0458C0.246993 12.4068 0.604993 12.6388 0.999994 12.6388H2.99999V19.6388C2.99999 19.904 3.10535 20.1584 3.29289 20.3459C3.48042 20.5334 3.73478 20.6388 3.99999 20.6388H6.99999C7.26521 20.6388 7.51956 20.5334 7.7071 20.3459C7.89464 20.1584 7.99999 19.904 7.99999 19.6388V15.6388H12V19.6388C12 19.904 12.1054 20.1584 12.2929 20.3459C12.4804 20.5334 12.7348 20.6388 13 20.6388H16C16.2652 20.6388 16.5196 20.5334 16.7071 20.3459C16.8946 20.1584 17 19.904 17 19.6388V12.6388H19C19.1937 12.6396 19.3834 12.5841 19.546 12.4789C19.7087 12.3738 19.8372 12.2236 19.916 12.0467C19.9947 11.8698 20.0203 11.6737 19.9896 11.4825C19.9589 11.2913 19.8732 11.1131 19.743 10.9698Z" fill="black"/> +</svg> diff --git a/front/src/assets/icons/key.png b/front/src/assets/icons/key.png Binary files differnew file mode 100644 index 0000000..2efe10b --- /dev/null +++ b/front/src/assets/icons/key.png diff --git a/front/src/assets/icons/key.svg b/front/src/assets/icons/key.svg new file mode 100644 index 0000000..c2ac4b9 --- /dev/null +++ b/front/src/assets/icons/key.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"> + <title>key</title> + <path d="M15 6a1.54 1.54 0 0 1-1.5-1.5 1.5 1.5 0 0 1 3 0A1.54 1.54 0 0 1 15 6zm-1.5-5A5.55 5.55 0 0 0 8 6.5a6.81 6.81 0 0 0 .7 2.8L1 17v2h4v-2h2v-2h2l3.2-3.2a5.85 5.85 0 0 0 1.3.2A5.55 5.55 0 0 0 19 6.5 5.55 5.55 0 0 0 13.5 1z"/> +</svg> diff --git a/front/src/assets/icons/logo.png b/front/src/assets/icons/logo.png Binary files differnew file mode 100644 index 0000000..fdb3f22 --- /dev/null +++ b/front/src/assets/icons/logo.png diff --git a/front/src/assets/icons/logo.svg b/front/src/assets/icons/logo.svg new file mode 100644 index 0000000..7cbac7c --- /dev/null +++ b/front/src/assets/icons/logo.svg @@ -0,0 +1,8 @@ + +<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="512" height="512" fill="#FBC917"/> +<circle cx="243.919" cy="230.964" r="93.6337" fill="#F2F2F2"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M286.864 217.687C280.478 224.095 275.619 235.177 275.619 241.105C275.619 258.196 254.334 275.749 227.149 300.24C168.195 349.594 174.912 352.009 195.507 343.446C198.439 342.227 204.883 338.748 206.382 338.748C207.881 338.748 160.103 367.905 165.093 370.063C170.091 369.978 228.767 344.936 237.613 338.729C254.826 326.655 265.055 325.162 268.152 331.525C269.767 334.843 267.568 336.52 267.202 341.159C266.627 348.449 268.715 350.068 275.079 350.068C280.407 348.92 285.397 340.62 288.209 336.47C291.669 331.361 299.546 323.724 309.092 315.216C323.222 304.015 326.84 293.677 323.222 266.107C324.834 242.962 328.757 238.648 343.382 231.645C362.164 224.408 369.59 221.114 350.792 217.687C346.228 216.855 329.961 215.088 323.222 214.241C317.199 213.485 316.816 208.727 306.335 208.727C295.449 210.58 292.197 212.337 286.864 217.687Z" fill="#111111"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M284.32 375.939L267.423 353.07C266.391 351.674 266.378 349.771 267.39 348.361L282.518 327.285L289.017 331.95L275.588 350.659L290.755 371.185L284.32 375.939Z" fill="#111111"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M273.729 374.67L273.073 372.258C271.741 372.619 270.954 373.993 271.316 375.325C271.677 376.652 273.04 377.438 274.367 377.087L274.371 377.086C274.383 377.083 274.41 377.077 274.454 377.067C274.542 377.047 274.698 377.015 274.928 376.973C275.39 376.89 276.151 376.773 277.274 376.652C279.521 376.412 283.207 376.163 288.833 376.163C294.458 376.163 298.379 376.411 300.869 376.655C302.114 376.776 303.001 376.896 303.564 376.983C303.845 377.027 304.045 377.062 304.168 377.085C304.23 377.096 304.272 377.105 304.296 377.11L304.316 377.114M304.314 377.113L304.318 377.114C305.665 377.404 306.993 376.55 307.287 375.203C307.582 373.854 306.727 372.522 305.378 372.228L304.845 374.67C305.378 372.228 305.377 372.227 305.377 372.227L305.372 372.226L305.365 372.225L305.345 372.221L305.284 372.208C305.235 372.198 305.167 372.185 305.08 372.169C304.907 372.137 304.657 372.093 304.328 372.042C303.668 371.94 302.689 371.809 301.355 371.678C298.689 371.418 294.604 371.163 288.833 371.163C283.064 371.163 279.197 371.418 276.741 371.681C275.513 371.812 274.634 371.946 274.044 372.052C273.749 372.105 273.526 372.151 273.367 372.187C273.287 372.204 273.224 372.219 273.175 372.231C273.151 372.237 273.13 372.243 273.113 372.247L273.091 372.253L273.081 372.255L273.077 372.257L273.075 372.257C273.075 372.257 273.073 372.258 273.729 374.67" fill="#111111"/> +</svg> diff --git a/front/src/assets/icons/messages.svg b/front/src/assets/icons/messages.svg new file mode 100644 index 0000000..8a0b9c3 --- /dev/null +++ b/front/src/assets/icons/messages.svg @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
+ <title>
+ message
+ </title>
+ <path d="M0 8v8a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8l-10 4z"/>
+ <path d="M18 2H2a2 2 0 0 0-2 2v2l10 4 10-4V4a2 2 0 0 0-2-2z"/>
+</svg>
diff --git a/front/src/assets/icons/nostr.svg b/front/src/assets/icons/nostr.svg new file mode 100644 index 0000000..80760a8 --- /dev/null +++ b/front/src/assets/icons/nostr.svg @@ -0,0 +1,3 @@ +<svg width="620" height="620" viewBox="0 0 620 620" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M457.468 262.483C469.349 344.787 384.156 362.202 335.143 360.68C331.996 360.582 329.03 362.114 327.171 364.655C322.101 371.586 313.996 380.549 307.739 380.549C300.712 380.549 294.388 394.063 291.504 402.47C291.201 403.353 292.069 404.167 292.957 403.879C370.58 378.675 401.039 370.03 414.161 372.46C424.754 374.421 444.567 409.885 453.149 427.372C431.816 428.598 424.705 409.476 419.801 406.044C415.877 403.299 414.406 415.85 414.161 422.469C409.502 420.018 403.617 416.586 403.126 406.044C402.636 395.503 397.241 398.2 393.073 398.69C388.904 399.18 324.904 419.772 315.341 422.469C305.778 425.166 294.743 428.843 285.671 435.216C270.467 442.571 260.659 437.423 257.962 424.92C255.804 414.918 269.814 384.145 277.088 370.008C268.097 373.358 249.428 380.157 246.682 380.549C244.019 380.93 210.235 407.011 192.169 421.181C191.115 422.008 190.437 423.219 190.194 424.536C187.304 440.241 178.078 444.637 162.084 452.867C149.214 459.49 104.263 528.566 81.1495 565.856C79.5513 568.434 77.2056 570.416 74.6869 572.106C60.0054 581.962 45.053 607.044 38.9885 619.32C30.3571 594.413 41.6041 572.988 48.3065 565.388C45.364 562.839 38.5798 564.326 35.5556 565.388C43.8927 536.706 73.0728 536.706 72.0919 536.706C77.4866 532.048 140.751 441.59 142.222 437.423C143.682 433.289 141.28 413.957 173.077 403.358C173.582 403.189 174.083 402.959 174.53 402.672C199.302 386.75 217.885 361.084 224.123 350.151C191.05 347.877 153.177 329.438 133.375 317.595C129.106 315.042 124.579 312.82 119.636 312.261C92.5231 309.192 65.3757 326.536 54.4368 336.423C49.9249 330.148 55.0089 311.909 58.1149 303.574C47.1295 302.005 30.9783 317.956 24.2759 326.127C17.0176 314.753 23.3768 294.422 27.4636 285.678C12.9471 286.071 3.106 292.706 0 295.975C12.0153 206.497 103.921 232.52 104.95 233.708C100.046 228.609 100.455 221.941 101.272 219.244C156.935 220.715 182.927 211.4 199.356 201.839C328.828 129.522 384.613 173.893 405.211 185.047C425.808 196.201 463.203 200.736 489.073 190.44C519.969 176.467 515.318 140.449 509.057 126.702C502.192 111.626 463.448 90.7882 449.716 64.4356C435.985 38.0831 447.95 6.03652 463.858 1.56985C479.014 -2.68593 489.838 2.26516 498.145 9.7683C503.684 14.7705 519.234 18.3484 525.732 20.6772C532.23 23.0061 543.019 26.0704 542.406 27.909C541.793 29.7476 531.39 29.5022 529.778 29.5022C526.467 29.5022 522.544 29.9925 526.467 31.8311C531.068 33.6968 538.013 36.4085 541.338 38.1936C541.77 38.4256 541.681 39.03 541.207 39.1574C518.642 45.2302 498.494 32.6994 483.555 47.1526C468.352 61.8612 500.72 73.1378 517.395 90.7882C534.069 108.439 551.234 129.521 537.502 177.569C526.871 214.767 482.316 246.393 459.302 258.764C457.979 259.476 457.253 260.996 457.468 262.483Z" fill="black"/>
+</svg>
diff --git a/front/src/assets/icons/pals.svg b/front/src/assets/icons/pals.svg new file mode 100644 index 0000000..04b17a3 --- /dev/null +++ b/front/src/assets/icons/pals.svg @@ -0,0 +1,3 @@ +<svg width="24" height="17" viewBox="0 0 24 17" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M7.2 8.63879C9.52125 8.63879 11.4 6.77961 11.4 4.48254C11.4 2.18547 9.52125 0.326294 7.2 0.326294C4.87875 0.326294 3 2.18547 3 4.48254C3 6.77961 4.87875 8.63879 7.2 8.63879ZM10.08 9.82629H9.76875C8.98875 10.1974 8.1225 10.42 7.2 10.42C6.2775 10.42 5.415 10.1974 4.63125 9.82629H4.32C1.935 9.82629 0 11.7411 0 14.1013V15.17C0 16.1534 0.80625 16.9513 1.8 16.9513H12.6C13.5938 16.9513 14.4 16.1534 14.4 15.17V14.1013C14.4 11.7411 12.465 9.82629 10.08 9.82629ZM18 8.63879C19.9875 8.63879 21.6 7.04309 21.6 5.07629C21.6 3.1095 19.9875 1.51379 18 1.51379C16.0125 1.51379 14.4 3.1095 14.4 5.07629C14.4 7.04309 16.0125 8.63879 18 8.63879ZM19.8 9.82629H19.6575C19.1363 10.0044 18.585 10.1232 18 10.1232C17.415 10.1232 16.8638 10.0044 16.3425 9.82629H16.2C15.435 9.82629 14.73 10.0452 14.1112 10.3978C15.0262 11.3738 15.6 12.6689 15.6 14.1013V15.5263C15.6 15.6079 15.5813 15.6859 15.5775 15.7638H22.2C23.1938 15.7638 24 14.9659 24 13.9825C24 11.6855 22.1213 9.82629 19.8 9.82629Z" fill="black"/> +</svg> diff --git a/front/src/assets/icons/profile.svg b/front/src/assets/icons/profile.svg new file mode 100644 index 0000000..f94e63d --- /dev/null +++ b/front/src/assets/icons/profile.svg @@ -0,0 +1,3 @@ +<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M5 4.63879C5 3.57793 5.42143 2.56051 6.17157 1.81037C6.92172 1.06022 7.93913 0.638794 9 0.638794C10.0609 0.638794 11.0783 1.06022 11.8284 1.81037C12.5786 2.56051 13 3.57793 13 4.63879C13 5.69966 12.5786 6.71708 11.8284 7.46722C11.0783 8.21737 10.0609 8.63879 9 8.63879C7.93913 8.63879 6.92172 8.21737 6.17157 7.46722C5.42143 6.71708 5 5.69966 5 4.63879ZM5 10.6388C3.67392 10.6388 2.40215 11.1656 1.46447 12.1033C0.526784 13.0409 0 14.3127 0 15.6388C0 16.4344 0.316071 17.1975 0.87868 17.7601C1.44129 18.3227 2.20435 18.6388 3 18.6388H15C15.7956 18.6388 16.5587 18.3227 17.1213 17.7601C17.6839 17.1975 18 16.4344 18 15.6388C18 14.3127 17.4732 13.0409 16.5355 12.1033C15.5979 11.1656 14.3261 10.6388 13 10.6388H5Z" fill="black"/> +</svg> diff --git a/front/src/assets/icons/quote.svg b/front/src/assets/icons/quote.svg new file mode 100644 index 0000000..5b847e3 --- /dev/null +++ b/front/src/assets/icons/quote.svg @@ -0,0 +1,3 @@ +<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M4.26338 0.906933C4.3437 0.846697 4.43509 0.80287 4.53234 0.777954C4.62959 0.753038 4.7308 0.747522 4.83019 0.761719C4.92957 0.775917 5.02519 0.809551 5.11157 0.860701C5.19796 0.911851 5.27342 0.979516 5.33366 1.05983C5.3939 1.14015 5.43772 1.23154 5.46264 1.32879C5.48756 1.42604 5.49307 1.52725 5.47887 1.62664C5.46468 1.72602 5.43104 1.82164 5.37989 1.90802C5.32874 1.99441 5.26108 2.06987 5.18076 2.13011C3.98511 3.02685 3.30013 3.89913 2.90795 4.66056C3.44643 4.52194 4.01472 4.55453 4.53382 4.75379C5.05293 4.95305 5.49707 5.30908 5.8045 5.7724C6.11193 6.23571 6.26738 6.7833 6.24925 7.33904C6.23112 7.89478 6.0403 8.43106 5.70332 8.87335C5.36634 9.31565 4.89994 9.64197 4.36895 9.80697C3.83796 9.97196 3.26876 9.96744 2.74046 9.79402C2.21216 9.6206 1.75101 9.2869 1.4211 8.83931C1.0912 8.39172 0.908937 7.85246 0.899644 7.2965C0.802772 6.35768 0.936068 5.40939 1.288 4.53365C1.7444 3.38234 2.63655 2.12705 4.26338 0.906933ZM11.1438 0.906933C11.2241 0.846697 11.3155 0.80287 11.4127 0.777954C11.51 0.753038 11.6112 0.747522 11.7106 0.761719C11.8099 0.775917 11.9056 0.809551 11.9919 0.860701C12.0783 0.911851 12.1538 0.979516 12.214 1.05983C12.2743 1.14015 12.3181 1.23154 12.343 1.32879C12.3679 1.42604 12.3734 1.52725 12.3592 1.62664C12.345 1.72602 12.3114 1.82164 12.2603 1.90802C12.2091 1.99441 12.1415 2.06987 12.0611 2.13011C10.8655 3.02685 10.1805 3.89913 9.78832 4.66056C10.3268 4.52194 10.8951 4.55453 11.4142 4.75379C11.9333 4.95305 12.3774 5.30908 12.6849 5.7724C12.9923 6.23571 13.1478 6.7833 13.1296 7.33904C13.1115 7.89478 12.9207 8.43106 12.5837 8.87335C12.2467 9.31565 11.7803 9.64197 11.2493 9.80697C10.7183 9.97196 10.1491 9.96744 9.62083 9.79402C9.09253 9.6206 8.63138 9.2869 8.30148 8.83931C7.97157 8.39172 7.78931 7.85246 7.78002 7.2965C7.68314 6.35768 7.81644 5.40939 8.16837 4.53365C8.62554 3.38234 9.51693 2.12705 11.1438 0.906933Z" fill="#757678"/> +</svg> diff --git a/front/src/assets/icons/radio.svg b/front/src/assets/icons/radio.svg new file mode 100644 index 0000000..5c98c15 --- /dev/null +++ b/front/src/assets/icons/radio.svg @@ -0,0 +1,3 @@ +<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M1.24 5.78879C0.51 6.06879 0 6.80879 0 7.63879V19.6388C0 20.7388 0.9 21.6388 2 21.6388H18C18.5304 21.6388 19.0391 21.4281 19.4142 21.053C19.7893 20.6779 20 20.1692 20 19.6388V7.63879C20 6.53879 19.1 5.63879 18 5.63879H6.3L13.73 2.63879C14.19 2.44879 14.41 1.92879 14.22 1.46879C14.1757 1.35955 14.1102 1.26015 14.0273 1.17632C13.9444 1.09249 13.8457 1.02589 13.737 0.980346C13.6282 0.934803 13.5115 0.911219 13.3936 0.910952C13.2757 0.910685 13.159 0.93374 13.05 0.97879L1.24 5.78879ZM5 19.6388C3.34 19.6388 2 18.2988 2 16.6388C2 14.9788 3.34 13.6388 5 13.6388C6.66 13.6388 8 14.9788 8 16.6388C8 18.2988 6.66 19.6388 5 19.6388ZM18 11.6388H16V10.6388C16 10.0888 15.55 9.63879 15 9.63879C14.45 9.63879 14 10.0888 14 10.6388V11.6388H2V8.63879C2 8.08879 2.45 7.63879 3 7.63879H17C17.55 7.63879 18 8.08879 18 8.63879V11.6388Z" fill="black"/> +</svg> diff --git a/front/src/assets/icons/reply.svg b/front/src/assets/icons/reply.svg new file mode 100644 index 0000000..db86cfd --- /dev/null +++ b/front/src/assets/icons/reply.svg @@ -0,0 +1,3 @@ +<svg width="19" height="21" viewBox="0 0 19 21" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M9.25452 13.4142L4.66867 18V13.4142H2.83434C1.82545 13.4142 1 12.5887 1 11.5798V3.3253C1 2.31642 1.82545 1.49097 2.83434 1.49097H15.6747C16.6836 1.49097 17.509 2.31642 17.509 3.3253V11.5798C17.509 12.5887 16.6836 13.4142 15.6747 13.4142H9.25452Z" stroke="#757678" stroke-width="2"/> +</svg> diff --git a/front/src/assets/icons/rt.svg b/front/src/assets/icons/rt.svg new file mode 100644 index 0000000..43b4a36 --- /dev/null +++ b/front/src/assets/icons/rt.svg @@ -0,0 +1,3 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M1.998 9.98763C1.998 10.4097 2.04387 10.8256 2.13562 11.2354C2.22736 11.6452 2.36193 12.0428 2.5393 12.4281C2.71668 12.8134 2.93076 13.1743 3.18153 13.5107C3.4323 13.8471 3.56262 13.9511 3.89291 14.2263L3.05802 15.0612C2.6788 14.7248 2.5026 14.5597 2.20901 14.1621C1.91543 13.7645 1.66465 13.3394 1.45669 12.8868C1.24873 12.4342 1.09276 11.9663 0.988783 11.4831C0.884803 10.9999 0.829755 10.5014 0.823639 9.98763C0.823639 9.33928 0.906211 8.71541 1.07136 8.11599C1.2365 7.51658 1.47504 6.95693 1.78698 6.43703C2.09892 5.91713 2.46591 5.44005 2.88794 5.00578C3.30998 4.57151 3.784 4.20452 4.31002 3.90482C4.83603 3.60511 5.39874 3.36963 5.99816 3.19837C6.59757 3.02711 7.22145 2.94148 7.86979 2.94148H13.7966L12.3012 1.44601L13.1269 0.620285L16.0353 3.52866L13.1269 6.43703L12.3012 5.61131L13.7966 4.11584H7.86979C7.33154 4.11584 6.81164 4.18618 6.31009 4.32685C5.80855 4.46753 5.34064 4.66326 4.90637 4.91403C4.4721 5.16481 4.07759 5.47063 3.72284 5.8315C3.36808 6.19237 3.06226 6.58994 2.80537 7.02421C2.54848 7.45848 2.34969 7.92638 2.20901 8.42793C2.06834 8.92948 1.998 9.44938 1.998 9.98763ZM17.4011 4.89568C17.7803 5.22596 17.9344 5.41558 18.228 5.81315C18.5216 6.21072 18.7724 6.63581 18.9803 7.08843C19.1883 7.54105 19.3443 8.01202 19.4482 8.50133C19.5522 8.99065 19.6073 9.48608 19.6134 9.98763C19.6134 10.636 19.5308 11.2599 19.3657 11.8593C19.2005 12.4587 18.962 13.0183 18.65 13.5382C18.3381 14.0581 17.9711 14.5352 17.5491 14.9695C17.127 15.4037 16.653 15.7707 16.127 16.0704C15.601 16.3701 15.0383 16.6056 14.4389 16.7769C13.8394 16.9481 13.2156 17.0338 12.5672 17.0338H6.64038L8.13586 18.5293L7.31013 19.355L4.40176 16.4466L7.31013 13.5382L8.13586 14.3639L6.64038 15.8594H12.5672C13.1055 15.8594 13.6254 15.7891 14.1269 15.6484C14.6285 15.5077 15.0964 15.312 15.5306 15.0612C15.9649 14.8104 16.3594 14.5046 16.7142 14.1438C17.0689 13.7829 17.3748 13.3853 17.6316 12.951C17.8885 12.5168 18.0873 12.0489 18.228 11.5473C18.3687 11.0458 18.439 10.5259 18.439 9.98763C18.439 9.55948 18.3931 9.1405 18.3014 8.7307C18.2096 8.3209 18.0781 7.92333 17.9069 7.53799C17.7356 7.15265 17.5215 6.79484 17.2647 6.46455C17.0078 6.13426 16.8996 5.9997 16.5754 5.71222L17.4011 4.89568Z" fill="#757678" stroke="#757678" stroke-width="0.5" stroke-linejoin="round"/> +</svg> diff --git a/front/src/assets/icons/rumors.svg b/front/src/assets/icons/rumors.svg new file mode 100644 index 0000000..2df5165 --- /dev/null +++ b/front/src/assets/icons/rumors.svg @@ -0,0 +1,3 @@ +<svg width="22" height="14" viewBox="0 0 22 14" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M11.022 0.263794C-3.5008 0.263794 -0.710755 13.0138 5.82254 13.0138C7.13176 13.0138 8.36453 12.3268 9.15038 11.1591L9.99465 9.90432C10.5085 9.14098 11.5359 9.14098 12.0497 9.90432L12.894 11.1591C13.6795 12.3268 14.9123 13.0138 16.2215 13.0138C22.4513 13.0138 25.7578 0.263794 11.022 0.263794ZM6.53753 8.37731C5.19024 8.37731 4.31415 7.52532 3.9099 7.01432C3.73829 6.7975 3.73829 6.48008 3.9099 6.26293C4.31415 5.75161 5.18992 4.89995 6.53753 4.89995C7.88514 4.89995 8.7609 5.75194 9.16515 6.26293C9.33676 6.47975 9.33676 6.79717 9.16515 7.01432C8.7609 7.52565 7.88481 8.37731 6.53753 8.37731ZM15.4625 8.37731C14.1152 8.37731 13.2392 7.52532 12.8349 7.01432C12.6633 6.7975 12.6633 6.48008 12.8349 6.26293C13.2392 5.75161 14.1149 4.89995 15.4625 4.89995C16.8101 4.89995 17.6859 5.75194 18.0902 6.26293C18.2618 6.47975 18.2618 6.79717 18.0902 7.01432C17.6859 7.52565 16.8098 8.37731 15.4625 8.37731Z" fill="black"/> +</svg> diff --git a/front/src/assets/icons/settings.svg b/front/src/assets/icons/settings.svg new file mode 100644 index 0000000..5c5e400 --- /dev/null +++ b/front/src/assets/icons/settings.svg @@ -0,0 +1,3 @@ +<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M10.012 0.888794C10.746 0.896794 11.477 0.981794 12.194 1.14179C12.3465 1.17584 12.4846 1.2567 12.5889 1.37305C12.6933 1.48941 12.7587 1.63546 12.776 1.79079L12.946 3.31779C12.97 3.53308 13.0442 3.73972 13.1626 3.9211C13.281 4.10248 13.4404 4.25352 13.6278 4.36208C13.8153 4.47064 14.0256 4.53369 14.2419 4.54614C14.4581 4.5586 14.6743 4.52012 14.873 4.43379L16.273 3.81879C16.4152 3.75614 16.5734 3.73924 16.7257 3.77041C16.878 3.80158 17.0168 3.87929 17.123 3.99279C18.135 5.07395 18.8889 6.37042 19.328 7.78479C19.3738 7.93353 19.3723 8.09286 19.3236 8.24068C19.2748 8.38851 19.1813 8.51751 19.056 8.60979L17.815 9.52579C17.6401 9.65396 17.4979 9.82152 17.3999 10.0149C17.3019 10.2083 17.2508 10.422 17.2508 10.6388C17.2508 10.8556 17.3019 11.0693 17.3999 11.2627C17.4979 11.4561 17.6401 11.6236 17.815 11.7518L19.058 12.6668C19.1835 12.7591 19.2772 12.8883 19.3259 13.0363C19.3747 13.1844 19.3761 13.3439 19.33 13.4928C18.8912 14.9071 18.1377 16.2035 17.126 17.2848C17.02 17.3983 16.8814 17.476 16.7293 17.5074C16.5772 17.5387 16.4192 17.5221 16.277 17.4598L14.871 16.8428C14.6726 16.7558 14.4565 16.7167 14.2402 16.7288C14.0238 16.7408 13.8134 16.8036 13.6259 16.9121C13.4383 17.0205 13.279 17.1716 13.1607 17.3531C13.0424 17.5346 12.9685 17.7414 12.945 17.9568L12.775 19.4828C12.758 19.6363 12.694 19.7808 12.5918 19.8966C12.4896 20.0124 12.3542 20.0939 12.204 20.1298C10.7556 20.4751 9.24634 20.4751 7.79797 20.1298C7.64757 20.094 7.5119 20.0127 7.4095 19.8969C7.30711 19.7811 7.24301 19.6364 7.22597 19.4828L7.05697 17.9588C7.03242 17.7441 6.95787 17.5382 6.83931 17.3575C6.72074 17.1768 6.56146 17.0265 6.37426 16.9186C6.18707 16.8106 5.97716 16.7481 5.76141 16.736C5.54566 16.7238 5.33008 16.7625 5.13197 16.8488L3.72597 17.4648C3.58363 17.5273 3.42537 17.5441 3.27309 17.5127C3.12081 17.4814 2.98204 17.4035 2.87597 17.2898C1.8641 16.2073 1.11088 14.9094 0.672971 13.4938C0.626868 13.3449 0.628288 13.1854 0.677034 13.0373C0.72578 12.8893 0.819431 12.7601 0.944971 12.6678L2.18797 11.7518C2.36282 11.6236 2.50501 11.4561 2.60302 11.2627C2.70103 11.0693 2.75211 10.8556 2.75211 10.6388C2.75211 10.422 2.70103 10.2083 2.60302 10.0149C2.50501 9.82152 2.36282 9.65396 2.18797 9.52579L0.944971 8.61179C0.819431 8.51944 0.72578 8.39027 0.677034 8.24224C0.628288 8.09421 0.626868 7.93467 0.672971 7.78579C1.11201 6.37142 1.8659 5.07495 2.87797 3.99379C2.98416 3.88029 3.12298 3.80258 3.27526 3.77141C3.42753 3.74024 3.58573 3.75714 3.72797 3.81979L5.12797 4.43479C5.32699 4.52107 5.54347 4.55946 5.76002 4.54688C5.97657 4.53431 6.18715 4.47112 6.37484 4.36239C6.56254 4.25366 6.72212 4.10243 6.84076 3.92084C6.9594 3.73924 7.0338 3.53236 7.05797 3.31679L7.22797 1.79079C7.24513 1.63515 7.31058 1.48878 7.41513 1.37221C7.51968 1.25564 7.6581 1.17472 7.81097 1.14079C8.52697 0.982127 9.26064 0.898127 10.012 0.888794ZM9.99997 7.63879C9.20432 7.63879 8.44126 7.95486 7.87865 8.51747C7.31604 9.08008 6.99997 9.84314 6.99997 10.6388C6.99997 11.4344 7.31604 12.1975 7.87865 12.7601C8.44126 13.3227 9.20432 13.6388 9.99997 13.6388C10.7956 13.6388 11.5587 13.3227 12.1213 12.7601C12.6839 12.1975 13 11.4344 13 10.6388C13 9.84314 12.6839 9.08008 12.1213 8.51747C11.5587 7.95486 10.7956 7.63879 9.99997 7.63879Z" fill="black"/> +</svg> diff --git a/front/src/assets/icons/youtube.svg b/front/src/assets/icons/youtube.svg new file mode 100644 index 0000000..46d7db9 --- /dev/null +++ b/front/src/assets/icons/youtube.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="800" width="1200" viewBox="-35.20005 -41.33325 305.0671 247.9995"><path d="M229.763 25.817c-2.699-10.162-10.65-18.165-20.748-20.881C190.716 0 117.333 0 117.333 0S43.951 0 25.651 4.936C15.553 7.652 7.6 15.655 4.903 25.817 0 44.236 0 82.667 0 82.667s0 38.429 4.903 56.85C7.6 149.68 15.553 157.681 25.65 160.4c18.3 4.934 91.682 4.934 91.682 4.934s73.383 0 91.682-4.934c10.098-2.718 18.049-10.72 20.748-20.882 4.904-18.421 4.904-56.85 4.904-56.85s0-38.431-4.904-56.85" fill="red"/><path d="M93.333 117.559l61.333-34.89-61.333-34.894z" fill="#fff"/></svg>
\ No newline at end of file diff --git a/front/src/assets/reacts/chad.png b/front/src/assets/reacts/chad.png Binary files differnew file mode 100644 index 0000000..51cfa0d --- /dev/null +++ b/front/src/assets/reacts/chad.png diff --git a/front/src/assets/reacts/cringe.png b/front/src/assets/reacts/cringe.png Binary files differnew file mode 100644 index 0000000..d39a50c --- /dev/null +++ b/front/src/assets/reacts/cringe.png diff --git a/front/src/assets/reacts/cry.png b/front/src/assets/reacts/cry.png Binary files differnew file mode 100644 index 0000000..f70fb28 --- /dev/null +++ b/front/src/assets/reacts/cry.png diff --git a/front/src/assets/reacts/doom.png b/front/src/assets/reacts/doom.png Binary files differnew file mode 100644 index 0000000..e6df1f4 --- /dev/null +++ b/front/src/assets/reacts/doom.png diff --git a/front/src/assets/reacts/facepalm.png b/front/src/assets/reacts/facepalm.png Binary files differnew file mode 100644 index 0000000..a03def9 --- /dev/null +++ b/front/src/assets/reacts/facepalm.png diff --git a/front/src/assets/reacts/galaxy.png b/front/src/assets/reacts/galaxy.png Binary files differnew file mode 100644 index 0000000..3c496d3 --- /dev/null +++ b/front/src/assets/reacts/galaxy.png diff --git a/front/src/assets/reacts/gigachad.png b/front/src/assets/reacts/gigachad.png Binary files differnew file mode 100644 index 0000000..5f3c2e1 --- /dev/null +++ b/front/src/assets/reacts/gigachad.png diff --git a/front/src/assets/reacts/pepechin.png b/front/src/assets/reacts/pepechin.png Binary files differnew file mode 100644 index 0000000..dafd907 --- /dev/null +++ b/front/src/assets/reacts/pepechin.png diff --git a/front/src/assets/reacts/pepeeyes.png b/front/src/assets/reacts/pepeeyes.png Binary files differnew file mode 100644 index 0000000..e57d5e6 --- /dev/null +++ b/front/src/assets/reacts/pepeeyes.png diff --git a/front/src/assets/reacts/pepegmi.png b/front/src/assets/reacts/pepegmi.png Binary files differnew file mode 100644 index 0000000..7c3cae4 --- /dev/null +++ b/front/src/assets/reacts/pepegmi.png diff --git a/front/src/assets/reacts/pepesad.png b/front/src/assets/reacts/pepesad.png Binary files differnew file mode 100644 index 0000000..51891fd --- /dev/null +++ b/front/src/assets/reacts/pepesad.png diff --git a/front/src/assets/reacts/pika.png b/front/src/assets/reacts/pika.png Binary files differnew file mode 100644 index 0000000..791594b --- /dev/null +++ b/front/src/assets/reacts/pika.png diff --git a/front/src/assets/reacts/pink.png b/front/src/assets/reacts/pink.png Binary files differnew file mode 100644 index 0000000..59fdc6a --- /dev/null +++ b/front/src/assets/reacts/pink.png diff --git a/front/src/assets/reacts/soy.png b/front/src/assets/reacts/soy.png Binary files differnew file mode 100644 index 0000000..33dbe33 --- /dev/null +++ b/front/src/assets/reacts/soy.png diff --git a/front/src/assets/reacts/yeschad.png b/front/src/assets/reacts/yeschad.png Binary files differnew file mode 100644 index 0000000..e001332 --- /dev/null +++ b/front/src/assets/reacts/yeschad.png diff --git a/front/src/assets/triangles.svg b/front/src/assets/triangles.svg new file mode 100644 index 0000000..0b45c01 --- /dev/null +++ b/front/src/assets/triangles.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"> +<g transform="translate(50 42)"> + <g transform="scale(0.8)"> + <g transform="translate(-50 -50)"> + <polygon fill="#9f1515" points="72.5 50 50 11 27.5 50 50 50"> + <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 38.5;360 50 38.5" keyTimes="0;1"></animateTransform> + </polygon> + <polygon fill="#f5e116" points="5 89 50 89 27.5 50"> + <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 27.5 77.5;360 27.5 77.5" keyTimes="0;1"></animateTransform> + </polygon> + <polygon fill="#04284d" points="72.5 50 50 89 95 89"> + <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 72.5 77.5;360 72 77.5" keyTimes="0;1"></animateTransform> + </polygon> + </g> + </g> +</g> +<!-- [ldio] generated by https://loading.io/ --></svg>
\ No newline at end of file diff --git a/front/src/components/Avatar.tsx b/front/src/components/Avatar.tsx new file mode 100644 index 0000000..35b4386 --- /dev/null +++ b/front/src/components/Avatar.tsx @@ -0,0 +1,59 @@ +import useLocalState from "@/state/state"; +import type { Ship } from "@/types/urbit"; +import Sigil from "./Sigil"; +import ShipModal from "./modals/ShipModal"; + +export default function ({ + p, + size, + color, + noClickOnName, +}: { + p: Ship; + size: number; + color?: string; + noClickOnName?: boolean; +}) { + const { setModal } = useLocalState(); + // TODO revisit this when %whom updates + const avatar = ( + <div className="avatar-w sigil cp" role="link" onClick={openModal}> + <Sigil patp={p} size={size} color={color} /> + </div> + ); + const tooLong = (s: string) => (s.length > 15 ? " too-long" : ""); + function openModal(e: React.MouseEvent) { + if (noClickOnName) return; + e.stopPropagation(); + setModal(<ShipModal ship={p} />); + } + const name = ( + <div className="name cp" role="link" onMouseUp={openModal}> + <p className={"p-only" + tooLong(p)}>{p.length > 28 ? "Anon" : p}</p> + </div> + ); + return ( + <div className="ship-avatar"> + {avatar} + {name} + </div> + ); +} + +export function SigilOnly({ p, size, color }: any) { + const { setModal } = useLocalState(); + function openModal(e: React.MouseEvent) { + e.stopPropagation(); + setModal(<ShipModal ship={p} />); + } + return ( + <div + className="avatar-w sigil cp" + role="link" + onClick={openModal} + onMouseUp={openModal} + > + <Sigil patp={p} size={size} color={color} /> + </div> + ); +} diff --git a/front/src/components/Sigil.tsx b/front/src/components/Sigil.tsx new file mode 100644 index 0000000..4978a72 --- /dev/null +++ b/front/src/components/Sigil.tsx @@ -0,0 +1,50 @@ +import comet from "@/assets/icons/comet.svg"; +import { auraToHex } from "@/logic/utils"; +import { isValidPatp } from "urbit-ob"; +import { sigil } from "urbit-sigils"; +import { reactRenderer } from "urbit-sigils"; + +interface SigilProps { + patp: string; + size: number; + color?: string; +} + +const Sigil = (props: SigilProps) => { + const color = props.color ? auraToHex(props.color) : "black"; + if (!isValidPatp(props.patp)) return <div className="sigil bad-sigil">X</div>; + else if (props.patp.length > 28) + return ( + <img + className="comet-icon" + src={comet} + alt="" + style={{ width: `${props.size}px`, height: `${props.size}px` }} + /> + ); + else if (props.patp.length > 15) + // moons + return ( + <> + {sigil({ + patp: props.patp.substring(props.patp.length - 13), + renderer: reactRenderer, + size: props.size, + colors: ["grey", "white"], + })} + </> + ); + else + return ( + <> + {sigil({ + patp: props.patp, + renderer: reactRenderer, + size: props.size, + colors: [color, "white"], + })} + </> + ); +}; + +export default Sigil; diff --git a/front/src/components/feed/Body.tsx b/front/src/components/feed/Body.tsx new file mode 100644 index 0000000..2f11962 --- /dev/null +++ b/front/src/components/feed/Body.tsx @@ -0,0 +1,174 @@ +import type { + // TODO ref backend fetching!! + Reference, + Block, + Inline, + Media as MediaType, + ExternalContent, +} from "@/types/trill"; +import crow from "@/assets/icons/crow.svg"; +import type { PostProps } from "./Post"; +import Media from "./Media"; +import JSONContent, { YoutubeSnippet } from "./External"; +import { useLocation } from "wouter"; +import Quote from "./Quote"; +import PostData from "./PostData"; +import Card from "./Card.tsx"; +import type { Ship } from "@/types/urbit.ts"; + +function Body(props: PostProps) { + const text = props.poast.contents.filter((c) => { + return ( + "paragraph" in c || + "blockquote" in c || + "heading" in c || + "codeblock" in c || + "list" in c + ); + }); + + const media: MediaType[] = props.poast.contents.filter( + (c): c is MediaType => "media" in c, + ); + + const refs = props.poast.contents.filter((c): c is Reference => "ref" in c); + const json = props.poast.contents.filter( + (c): c is ExternalContent => "json" in c, + ); + + return ( + <div className="trill-post-body body"> + <div className="body-text"> + {text.map((b, i) => ( + <TextBlock key={JSON.stringify(b) + i} block={b} /> + ))} + </div> + {media.length > 0 && <Media media={media} />} + {refs.map((r, i) => ( + <Ref r={r} nest={props.nest || 0} key={JSON.stringify(r) + i} /> + ))} + <JSONContent content={json} /> + </div> + ); +} +export default Body; + +function TextBlock({ block }: { block: Block }) { + const key = JSON.stringify(block); + return "paragraph" in block ? ( + <div className="trill-post-paragraph"> + {block.paragraph.map((i, ind) => ( + <Inlin key={key + ind} i={i} /> + ))} + </div> + ) : "blockquote" in block ? ( + <blockquote> + {block.blockquote.map((i, ind) => ( + <Inlin key={key + ind} i={i} /> + ))} + </blockquote> + ) : "heading" in block ? ( + <Heading string={block.heading.text} num={block.heading.num} /> + ) : "codeblock" in block ? ( + <pre> + <code className={`language-${block.codeblock.lang}`}> + {block.codeblock.code} + </code> + </pre> + ) : "list" in block ? ( + block.list.ordered ? ( + <ol> + {block.list.text.map((i, ind) => ( + <li key={JSON.stringify(i) + ind}> + <Inlin key={key + ind} i={i} /> + </li> + ))} + </ol> + ) : ( + <ul> + {block.list.text.map((i, ind) => ( + <li key={JSON.stringify(i) + ind}> + <Inlin key={JSON.stringify(i) + ind} i={i} /> + </li> + ))} + </ul> + ) + ) : null; +} +function Inlin({ i }: { i: Inline }) { + const [_, navigate] = useLocation(); + function gotoShip(e: React.MouseEvent, ship: Ship) { + e.stopPropagation(); + navigate(`/feed/${ship}`); + } + return "text" in i ? ( + <span>{i.text}</span> + ) : "italic" in i ? ( + <i>{i.italic}</i> + ) : "bold" in i ? ( + <strong>{i.bold}</strong> + ) : "strike" in i ? ( + <span>{i.strike}</span> + ) : "underline" in i ? ( + <span>{i.underline}</span> + ) : "sup" in i ? ( + <sup>{i.sup}</sup> + ) : "sub" in i ? ( + <sub>{i.sub}</sub> + ) : "ship" in i ? ( + <span + className="mention" + role="link" + onMouseUp={(e) => gotoShip(e, i.ship)} + > + {i.ship} + </span> + ) : "codespan" in i ? ( + <code>{i.codespan}</code> + ) : "link" in i ? ( + <LinkParser {...i.link} /> + ) : "break" in i ? ( + <br /> + ) : null; +} + +function LinkParser({ href, show }: { href: string; show: string }) { + const YOUTUBE_REGEX_1 = /(youtube\.com\/watch\?v=)(\w+)/; + const YOUTUBE_REGEX_2 = /(youtu\.be\/)([a-zA-Z0-9-_]+)/; + const m1 = href.match(YOUTUBE_REGEX_1); + const m2 = href.match(YOUTUBE_REGEX_2); + const ytb = m1 && m1[2] ? m1[2] : m2 && m2[2] ? m2[2] : ""; + return ytb ? ( + <YoutubeSnippet href={href} id={ytb} /> + ) : ( + <a href={href}>{show}</a> + ); +} +function Heading({ string, num }: { string: string; num: number }) { + return num === 1 ? ( + <h1>{string}</h1> + ) : num === 2 ? ( + <h2>{string}</h2> + ) : num === 3 ? ( + <h3>{string}</h3> + ) : num === 4 ? ( + <h4>{string}</h4> + ) : num === 5 ? ( + <h5>{string}</h5> + ) : num === 6 ? ( + <h6>{string}</h6> + ) : null; +} + +function Ref({ r, nest }: { r: Reference; nest: number }) { + if (r.ref.type === "nostril") { + const comp = PostData({ + host: r.ref.ship, + id: r.ref.path.slice(1), + nest: nest + 1, + className: "quote-in-post", + })(Quote); + return <Card logo={crow}>{comp}</Card>; + } + return <></>; +} diff --git a/front/src/components/feed/Card.tsx b/front/src/components/feed/Card.tsx new file mode 100644 index 0000000..37f4911 --- /dev/null +++ b/front/src/components/feed/Card.tsx @@ -0,0 +1,9 @@ +export default function ({ children, logo, cn}: { cn?: string; logo: string; children: any }) { + const className = "trill-post-card" + (cn ? ` ${cn}`: "") + return ( + <div className={className}> + <img src={logo} alt="" className="trill-post-card-logo" /> + {children} + </div> + ); +} diff --git a/front/src/components/feed/Composer.tsx b/front/src/components/feed/Composer.tsx new file mode 100644 index 0000000..27da392 --- /dev/null +++ b/front/src/components/feed/Composer.tsx @@ -0,0 +1,52 @@ +import { openLock } from "@/logic/bunts"; +import { HASHTAGS_REGEX } from "@/logic/constants"; +import useLocalState from "@/state/state"; +import type { Poast, SentPoast } from "@/types/trill"; +import Sigil from "@/components/Sigil"; +import { useState } from "react"; + +function Composer({ + isAnon, + replying, +}: { + isAnon?: boolean; + replying?: Poast; +}) { + const { api, keys } = useLocalState(); + const our = api!.airlock.our!; + const [input, setInput] = useState(replying ? `${replying}: ` : ""); + async function poast() { + // TODO + // const parent = replying ? replying : null; + // const tokens = tokenize(input); + // const post: SentPoast = { + // host: parent ? parent.host : our, + // author: our, + // thread: parent ? parent.thread : null, + // parent: parent ? parent.id : null, + // contents: input, + // read: openLock, + // write: openLock, + // tags: input.match(HASHTAGS_REGEX) || [], + // }; + // TODO make it user choosable + const pubkey = keys[0]!; + await api!.addPost(pubkey, input); + } + const placeHolder = isAnon ? "> be me" : "What's going on in Urbit"; + return ( + <div id="composer"> + <div className="sigil"> + <Sigil patp={our} size={48} /> + </div> + <input + value={input} + onInput={(e) => setInput(e.currentTarget.value)} + placeholder={placeHolder} + /> + <button onClick={poast}>Post</button> + </div> + ); +} + +export default Composer; diff --git a/front/src/components/feed/External.tsx b/front/src/components/feed/External.tsx new file mode 100644 index 0000000..0ea1500 --- /dev/null +++ b/front/src/components/feed/External.tsx @@ -0,0 +1,41 @@ +import type { ExternalContent } from "@/types/trill"; +import youtube from "@/assets/icons/youtube.svg"; +import Card from "./Card"; + +interface JSONProps { + content: ExternalContent[]; +} + +function JSONContent({ content }: JSONProps) { + return ( + <> + {content.map((c, i) => { + if (!JSON.parse(c.json.content)) return <p key={i}>Error</p>; + else + return ( + <p + key={JSON.stringify(c.json)} + className="external-content-warning" + > + External content from "{c.json.origin}", use + <a href="https://urbit.org/applications/~sortug/ufa">UFA</a> + to display. + </p> + ); + })} + </> + ); +} +export default JSONContent; + +export function YoutubeSnippet({ href, id }: { href: string; id: string }) { + const thumbnail = `https://i.ytimg.com/vi/${id}/hqdefault.jpg`; + // todo styiling + return ( + <Card logo={youtube} cn="youtube-thumbnail"> + <a href={href}> + <img src={thumbnail} alt="" /> + </a> + </Card> + ); +} diff --git a/front/src/components/feed/Footer.tsx b/front/src/components/feed/Footer.tsx new file mode 100644 index 0000000..938a8c7 --- /dev/null +++ b/front/src/components/feed/Footer.tsx @@ -0,0 +1,237 @@ +import type { PostProps } from "./Post"; +import reply from "@/assets/icons/reply.svg"; +import quote from "@/assets/icons/quote.svg"; +import repost from "@/assets/icons/rt.svg"; +import { useState } from "react"; +import useLocalState from "@/state/state"; +import { useLocation } from "wouter"; +import { displayCount } from "@/logic/utils"; +import { TrillReactModal, stringToReact } from "./Reactions"; +import toast from "react-hot-toast"; +import NostrIcon from "./NostrIcon"; +function Footer({ poast, refetch }: PostProps) { + const [_showMenu, setShowMenu] = useState(false); + const [location, navigate] = useLocation(); + const [reposting, _setReposting] = useState(false); + const { api, setComposerData, setModal } = useLocalState(); + const our = api!.airlock.our!; + function doReply(e: React.MouseEvent) { + e.stopPropagation(); + setComposerData({ type: "reply", post: { service: "trill", post: poast } }); + navigate("/composer"); + } + function doQuote(e: React.MouseEvent) { + e.stopPropagation(); + setComposerData({ + type: "quote", + post: { service: "trill", post: poast }, + }); + navigate("/composer"); + } + const childrenCount = poast.children + ? poast.children.length + ? poast.children.length + : Object.keys(poast.children).length + : 0; + const myRP = poast.engagement.shared.find((r) => r.pid.ship === our); + async function cancelRP(e: React.MouseEvent) { + e.stopPropagation(); + const r = await api!.deletePost(our); + if (r) toast.success("Repost deleted"); + refetch(); + if (location.includes(poast.id)) navigate("/"); + } + async function sendRP(e: React.MouseEvent) { + // TODO update backend because contents are only markdown now + e.stopPropagation(); + // const c = [ + // { + // ref: { + // type: "trill", + // ship: poast.host, + // path: `/${poast.id}`, + // }, + // }, + // ]; + // const post: SentPoast = { + // host: our, + // author: our, + // thread: null, + // parent: null, + // contents: input, + // read: openLock, + // write: openLock, + // tags: [], // TODO + // }; + // const r = await api!.addPost(post, false); + // setReposting(true); + // if (r) { + // setReposting(false); + // toast.success("Your post was published"); + // } + } + function doReact(e: React.MouseEvent) { + e.stopPropagation(); + const modal = <TrillReactModal poast={poast} />; + setModal(modal); + } + function showReplyCount() { + if (poast.children[0]) fetchAndShow(); // Flatpoast + // else { + // const authors = Object.keys(poast.children).map( + // (i) => poast.children[i].post.author + // ); + // setEngagement({ type: "replies", ships: authors }, poast); + // } + } + async function fetchAndShow() { + // let authors = []; + // for (let i of poast.children as string[]) { + // const res = await scrypoastFull(poast.host, i); + // if (res) + // authors.push(res.post.author || "deleter"); + // } + // setEngagement({ type: "replies", ships: authors }, poast); + } + function showRepostCount() { + // const ships = poast.engagement.shared.map((entry) => entry.host); + // setEngagement({ type: "reposts", ships: ships }, poast); + } + function showQuoteCount() { + // setEngagement({ type: "quotes", quotes: poast.engagement.quoted }, poast); + } + function showReactCount() { + // setEngagement({ type: "reacts", reacts: poast.engagement.reacts }, poast); + } + + const mostCommonReact = Object.values(poast.engagement.reacts).reduce( + (acc: any, item) => { + if (!acc.counts[item]) acc.counts[item] = 0; + acc.counts[item] += 1; + if (!acc.winner || acc.counts[item] > acc.counts[acc.winner]) + acc.winner = item; + return acc; + }, + { counts: {}, winner: "" }, + ).winner; + const reactIcon = stringToReact(mostCommonReact); + + // TODO round up all helpers + + return ( + <div className="footer-wrapper post-footer"> + <footer> + <div className="icon"> + <span role="link" onMouseUp={showReplyCount} className="reply-count"> + {displayCount(childrenCount)} + </span> + <img role="link" onMouseUp={doReply} src={reply} alt="" /> + </div> + <div className="icon"> + <span role="link" onMouseUp={showQuoteCount} className="quote-count"> + {displayCount(poast.engagement.quoted.length)} + </span> + <img role="link" onMouseUp={doQuote} src={quote} alt="" /> + </div> + <div className="icon"> + <span + role="link" + onMouseUp={showRepostCount} + className="repost-count" + > + {displayCount(poast.engagement.shared.length)} + </span> + {reposting ? ( + <p>...</p> + ) : myRP ? ( + <img + role="link" + className="my-rp" + onMouseUp={cancelRP} + src={repost} + title="cancel repost" + /> + ) : ( + <img role="link" onMouseUp={sendRP} src={repost} title="repost" /> + )} + </div> + <div className="icon" role="link" onMouseUp={doReact}> + <span + role="link" + onMouseUp={showReactCount} + className="reaction-count" + > + {displayCount(Object.keys(poast.engagement.reacts).length)} + </span> + {reactIcon} + </div> + <NostrIcon poast={poast} /> + </footer> + </div> + ); +} +export default Footer; + +// function Menu({ +// poast, +// setShowMenu, +// refetch, +// }: { +// poast: Poast; +// setShowMenu: Function; +// refetch: Function; +// }) { +// const ref = useRef<HTMLDivElement>(null); +// const [location, navigate] = useLocation(); +// // TODO this is a mess and the event still propagates +// useEffect(() => { +// const checkIfClickedOutside = (e: any) => { +// e.stopPropagation(); +// if (ref && ref.current && !ref.current.contains(e.target)) +// setShowMenu(false); +// }; +// document.addEventListener("mousedown", checkIfClickedOutside); +// return () => { +// document.removeEventListener("mousedown", checkIfClickedOutside); +// }; +// }, []); +// const { our, setModal, setAlert } = useLocalState(); +// const mine = our === poast.host || our === poast.author; +// async function doDelete(e: React.MouseEvent) { +// e.stopPropagation(); +// deletePost(poast.host, poast.id); +// setAlert("Post deleted"); +// setShowMenu(false); +// refetch(); +// if (location.includes(poast.id)) navigate("/"); +// } +// async function copyLink(e: React.MouseEvent) { +// e.stopPropagation(); +// const link = trillPermalink(poast); +// await navigator.clipboard.writeText(link); +// // some alert +// setShowMenu(false); +// } +// function openStats(e: React.MouseEvent) { +// e.stopPropagation(); +// e.preventDefault(); +// const m = <StatsModal poast={poast} close={() => setModal(null)} />; +// setModal(m); +// } +// return ( +// <div ref={ref} id="post-menu"> +// {/* <p onClick={openShare}>Share to Groups</p> */} +// <p role="link" onMouseUp={openStats}> +// See Stats +// </p> +// <p role="link" onMouseUp={copyLink}> +// Permalink +// </p> +// {mine && ( +// <p role="link" onMouseUp={doDelete}> +// Delete Post +// </p> +// )} +// </div> +// ); +// } diff --git a/front/src/components/feed/Header.tsx b/front/src/components/feed/Header.tsx new file mode 100644 index 0000000..7658bfb --- /dev/null +++ b/front/src/components/feed/Header.tsx @@ -0,0 +1,33 @@ +import { date_diff } from "@/logic/utils"; +import type { PostProps } from "./Post"; +import { useLocation } from "wouter"; +function Header(props: PostProps) { + const [_, navigate] = useLocation(); + function go(e: React.MouseEvent) { + e.stopPropagation(); + } + function openThread(e: React.MouseEvent) { + e.stopPropagation(); + const sel = window.getSelection()?.toString(); + if (!sel) navigate(`/feed/${poast.host}/${poast.id}`); + } + const { poast } = props; + const name = ( + <div className="name cp"> + <p className="p-only">{poast.author}</p> + </div> + ); + return ( + <header> + <div className="author flex-align" role="link" onMouseUp={go}> + {name} + </div> + <div role="link" onMouseUp={openThread} className="date"> + <p title={new Date(poast.time).toLocaleString()}> + {date_diff(poast.time, "short")} + </p> + </div> + </header> + ); +} +export default Header; diff --git a/front/src/components/feed/Media.tsx b/front/src/components/feed/Media.tsx new file mode 100644 index 0000000..04ea156 --- /dev/null +++ b/front/src/components/feed/Media.tsx @@ -0,0 +1,35 @@ +import type { Media } from "@/types/trill"; +interface Props { + media: Media[]; +} +function M({ media }: Props) { + return ( + <div className="body-media"> + {media.map((m, i) => { + return "video" in m.media ? ( + <video key={JSON.stringify(m) + i} src={m.media.video} controls /> + ) : "audio" in m.media ? ( + <audio key={JSON.stringify(m) + i} src={m.media.audio} controls /> + ) : "images" in m.media ? ( + <Images key={JSON.stringify(m) + i} urls={m.media.images} /> + ) : null; + })} + </div> + ); +} +export default M; + +function Images({ urls }: { urls: string[] }) { + return ( + <> + {urls.map((u, i) => ( + <img + key={u + i} + className={`body-img body-img-1-of-${urls.length}`} + src={u} + alt="" + /> + ))} + </> + ); +} diff --git a/front/src/components/feed/NostrIcon.tsx b/front/src/components/feed/NostrIcon.tsx new file mode 100644 index 0000000..0c368fb --- /dev/null +++ b/front/src/components/feed/NostrIcon.tsx @@ -0,0 +1,22 @@ +import nostrIcon from "@/assets/icons/nostr.svg"; +import useLocalState from "@/state/state"; +import toast from "react-hot-toast"; +import type { Poast } from "@/types/trill"; +export default function ({ poast }: { poast: Poast }) { + const { relays, api, keys } = useLocalState(); + + async function sendToRelay(e: React.MouseEvent) { + e.stopPropagation(); + // + const urls = Object.keys(relays); + await api!.relayPost(poast.host, poast.id, urls); + toast.success("Post relayed"); + } + // TODO round up all helpers + + return ( + <div className="icon" role="link" onMouseUp={sendToRelay}> + <img role="link" src={nostrIcon} title="repost" /> + </div> + ); +} diff --git a/front/src/components/feed/Post.tsx b/front/src/components/feed/Post.tsx new file mode 100644 index 0000000..1211a97 --- /dev/null +++ b/front/src/components/feed/Post.tsx @@ -0,0 +1,79 @@ +import type { PostID, Poast, Reference } from "@/types/trill"; + +import Header from "./Header"; +import Body from "./Body"; +import Footer from "./Footer"; +import { useLocation } from "wouter"; +import useLocalState from "@/state/state"; +import RP from "./RP"; +import ShipModal from "../modals/ShipModal"; +import type { Ship } from "@/types/urbit"; +import Sigil from "../Sigil"; + +export interface PostProps { + poast: Poast; + fake?: boolean; + rter?: Ship; + rtat?: number; + rtid?: PostID; + nest?: number; + refetch: Function; +} +function Post(props: PostProps) { + const { poast } = props; + console.log({ poast }); + if (!poast || poast.contents === null) { + return null; + } + const isRP = + poast.contents.length === 1 && + "ref" in poast.contents[0] && + poast.contents[0].ref.type === "trill"; + if (isRP) { + const ref = (poast.contents[0] as Reference).ref; + return ( + <RP + host={ref.ship} + id={ref.path.slice(1)} + rter={poast.author} + rtat={poast.time} + rtid={poast.id} + /> + ); + } else return <TrillPost {...props} />; +} +export default Post; + +function TrillPost(props: PostProps) { + const { poast, fake } = props; + const { setModal } = useLocalState(); + const [_, navigate] = useLocation(); + function openThread(_e: React.MouseEvent) { + const sel = window.getSelection()?.toString(); + if (!sel) navigate(`/feed/${poast.host}/${poast.id}`); + } + + function openModal(e: React.MouseEvent) { + e.stopPropagation(); + setModal(<ShipModal ship={poast.author} />); + } + const avatar = ( + <div className="avatar-w sigil cp" role="link" onMouseUp={openModal}> + <Sigil patp={poast.author} size={42} /> + </div> + ); + return ( + <div + className={`timeline-post trill-post cp`} + role="link" + onMouseUp={openThread} + > + <div className="left">{avatar}</div> + <div className="right"> + <Header {...props} /> + <Body {...props} /> + {!fake && <Footer {...props} />} + </div> + </div> + ); +} diff --git a/front/src/components/feed/PostData.tsx b/front/src/components/feed/PostData.tsx new file mode 100644 index 0000000..f3c4715 --- /dev/null +++ b/front/src/components/feed/PostData.tsx @@ -0,0 +1,160 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import spinner from "@/assets/triangles.svg"; +import { useEffect, useRef, useState } from "react"; +import useLocalState from "@/state/state"; +import type { PostID } from "@/types/trill"; +import type { Ship } from "@/types/urbit"; + +function PostData(props: { + host: Ship; + id: PostID; + rter?: Ship; + rtat?: number; + rtid?: PostID; + nest?: number; // nested quotes + className?: string; +}) { + const { api } = useLocalState(); + const { host, id, nest } = props; + const [enest, setEnest] = useState(nest); + useEffect(() => { + setEnest(nest); + }, [nest]); + + return function (Component: React.ElementType) { + // const [showNested, setShowNested] = useState(nest <= 3); + const handleShowNested = (e: React.MouseEvent) => { + e.stopPropagation(); + setEnest(enest! - 3); + }; + const [dead, setDead] = useState(false); + const [denied, setDenied] = useState(false); + const { isLoading, isError, data, refetch } = useQuery({ + queryKey: ["trill-thread", host, id], + queryFn: fetchNode, + }); + const queryClient = useQueryClient(); + const dataRef = useRef(data); + useEffect(() => { + dataRef.current = data; + }, [data]); + + async function fetchNode(): Promise<any> { + const res = await api!.scryPost(host, id, null, null); + if ("fpost" in res) return res; + else { + const existing = queryClient.getQueryData(["trill-thread", host, id]); + const existingData = existing || data; + if ("bugen" in res) { + // we peek for the actual node + peekTheNode(); + // if we have a cache we don't invalidate it + if (existingData && "fpost" in existingData) return existingData; + // if we don't have a cache then we show the loading screen + else return res; + } + if ("no-node" in res) { + if (existingData && "fpost" in existingData) return existingData; + else return res; + } + } + } + function peekTheNode() { + let timer; + peekNode({ ship: host, id }); + timer = setTimeout(() => { + const gotPost = dataRef.current && "fpost" in dataRef.current; + setDead(!gotPost); + // clearTimeout(timer); + }, 10_000); + } + + useEffect(() => { + const path = `${host}/${id}`; + if (path in peekedPosts) { + queryClient.setQueryData(["trill-thread", host, id], { + fpost: peekedPosts[path], + }); + } else if (path in deniedPosts) { + setDenied(true); + } + }, [peekedPosts]); + useEffect(() => { + const path = `${host}/${id}`; + if (path in deniedPosts) setDenied(true); + }, [deniedPosts]); + + useEffect(() => { + const l = lastThread; + if (l && l.thread == id) { + queryClient.setQueryData(["trill-thread", host, id], { fpost: l }); + } + }, [lastThread]); + function retryPeek(e: React.MouseEvent) { + e.stopPropagation(); + setDead(false); + peekTheNode(); + } + if (enest > 3) + return ( + <div className={props.className}> + <div className="lazy x-center not-found"> + <button className="x-center" onMouseUp={handleShowNested}> + Load more + </button> + </div> + </div> + ); + else + return data ? ( + dead ? ( + <div className={props.className}> + <div className="no-response x-center not-found"> + <p>{host} did not respond</p> + <button className="x-center" onMouseUp={retryPeek}> + Try again + </button> + </div> + </div> + ) : denied ? ( + <div className={props.className}> + <p className="x-center not-found"> + {host} denied you access to this post + </p> + </div> + ) : "no-node" in data || "bucun" in data ? ( + <div className={props.className}> + <p className="x-center not-found">Post not found</p> + </div> + ) : "bugen" in data ? ( + <div className={props.className}> + <div className="x-center not-found"> + <p className="x-center">Post not found, requesting...</p> + <img src={spinner} className="x-center s-100" alt="" /> + </div> + </div> + ) : "fpost" in data && data.fpost.contents === null ? ( + <div className={props.className}> + <p className="x-center not-found">Post deleted</p> + </div> + ) : ( + <Component + data={data.fpost} + refetch={refetch} + {...props} + nest={enest} + /> + ) + ) : // no data + isLoading || isError ? ( + <div className={props.className}> + <img className="x-center post-spinner" src={spinner} alt="" /> + </div> + ) : ( + <div className={props.className}> + <p>...</p> + </div> + ); + }; +} +export default PostData; diff --git a/front/src/components/feed/PostList.tsx b/front/src/components/feed/PostList.tsx new file mode 100644 index 0000000..3d41ff8 --- /dev/null +++ b/front/src/components/feed/PostList.tsx @@ -0,0 +1,32 @@ +import TrillPost from "./Post"; +import type { FC } from "@/types/trill"; +// import { useEffect } from "react"; +// import { useQueryClient } from "@tanstack/react-query"; +// import { toFull } from "../thread/helpers"; + +function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) { + // const qc = useQueryClient(); + // useEffect(() => { + // Object.values(data.feed).forEach((poast) => { + // const queryKey = ["trill-thread", poast.host, poast.id]; + // const existing = qc.getQueryData(queryKey); + // if (!existing || !("fpost" in (existing as any))) { + // qc.setQueryData(queryKey, { + // fpost: toFull(poast), + // }); + // } + // }); + // }, [data]); + return ( + <> + {Object.keys(data.feed) + .sort() + .reverse() + .map((i) => ( + <TrillPost key={i} poast={data.feed[i]} refetch={refetch} /> + ))} + </> + ); +} + +export default TrillFeed; diff --git a/front/src/components/feed/Quote.tsx b/front/src/components/feed/Quote.tsx new file mode 100644 index 0000000..d71be40 --- /dev/null +++ b/front/src/components/feed/Quote.tsx @@ -0,0 +1,37 @@ +import type { FullNode } from "@/types/trill"; +import { date_diff } from "@/logic/utils"; +import { useLocation } from "wouter"; +import Body from "./Body"; +import Sigil from "../Sigil"; +import { toFlat } from "./RP"; + +function Quote({ + data, + refetch, + nest, +}: { + data: FullNode; + refetch?: Function; + nest: number; +}) { + const [_, navigate] = useLocation(); + function gotoQuote(e: React.MouseEvent) { + e.stopPropagation(); + navigate(`/feed/${data.host}/${data.id}`); + } + return ( + <div onMouseUp={gotoQuote} className="quote-in-post"> + <header className="btw"> + ( + <div className="quote-author flex"> + <Sigil patp={data.author} size={20} /> + {data.author} + </div> + )<span>{date_diff(data.time, "short")}</span> + </header> + <Body poast={toFlat(data)} nest={nest} refetch={refetch!} /> + </div> + ); +} + +export default Quote; diff --git a/front/src/components/feed/RP.tsx b/front/src/components/feed/RP.tsx new file mode 100644 index 0000000..dc733cc --- /dev/null +++ b/front/src/components/feed/RP.tsx @@ -0,0 +1,47 @@ +import Post from "./Post"; +import type { Ship } from "@/types/urbit"; +import type { Poast, FullNode, ID } from "@/types/trill"; +import PostData from "./PostData"; +export default function (props: { + host: string; + id: string; + rter: Ship; + rtat: number; + rtid: ID; + refetch?: Function; +}) { + return PostData(props)(RP); +} + +function RP({ + data, + refetch, + rter, + rtat, + rtid, +}: { + data: FullNode; + refetch: Function; + rter: Ship; + rtat: number; + rtid: ID; +}) { + return ( + <Post + poast={toFlat(data)} + rter={rter} + rtat={rtat} + rtid={rtid} + refetch={refetch} + /> + ); +} + +export function toFlat(n: FullNode): Poast { + return { + ...n, + children: !n.children + ? [] + : Object.keys(n.children).map((c) => n.children[c].id), + }; +} diff --git a/front/src/components/feed/Reactions.tsx b/front/src/components/feed/Reactions.tsx new file mode 100644 index 0000000..58662cd --- /dev/null +++ b/front/src/components/feed/Reactions.tsx @@ -0,0 +1,118 @@ +import type { Poast } from "@/types/trill"; +import yeschad from "@/assets/reacts/yeschad.png"; +import cringe from "@/assets/reacts/cringe.png"; +import cry from "@/assets/reacts/cry.png"; +import doom from "@/assets/reacts/doom.png"; +import galaxy from "@/assets/reacts/galaxy.png"; +import gigachad from "@/assets/reacts/gigachad.png"; +import pepechin from "@/assets/reacts/pepechin.png"; +import pepeeyes from "@/assets/reacts/pepeeyes.png"; +import pepegmi from "@/assets/reacts/pepegmi.png"; +import pepesad from "@/assets/reacts/pepesad.png"; +import pink from "@/assets/reacts/pink.png"; +import soy from "@/assets/reacts/soy.png"; +import chad from "@/assets/reacts/chad.png"; +import pika from "@/assets/reacts/pika.png"; +import facepalm from "@/assets/reacts/facepalm.png"; +import emoji from "@/assets/icons/emoji.svg"; +import emojis from "@/logic/emojis.json"; +import Modal from "../modals/Modal"; +import useLocalState from "@/state/state"; + +export function ReactModal({ send }: { send: (s: string) => Promise<number> }) { + const { setModal } = useLocalState(); + async function sendReact(e: React.MouseEvent, s: string) { + e.stopPropagation(); + const res = await send(s); + if (res) setModal(null); + } + // todo one more meme + return ( + <Modal> + <div id="react-list"> + <span onMouseUp={(e) => sendReact(e, "❤️")}>️️❤️</span> + <span onMouseUp={(e) => sendReact(e, "🤔")}>🤔</span> + <span onMouseUp={(e) => sendReact(e, "😅")}>😅</span> + <span onMouseUp={(e) => sendReact(e, "🤬")}>🤬</span> + <span onMouseUp={(e) => sendReact(e, "😂")}>😂️</span> + <span onMouseUp={(e) => sendReact(e, "🫡")}>🫡️</span> + <span onMouseUp={(e) => sendReact(e, "🤢")}>🤢</span> + <span onMouseUp={(e) => sendReact(e, "😭")}>😭</span> + <span onMouseUp={(e) => sendReact(e, "😱")}>😱</span> + <img + onMouseUp={(e) => sendReact(e, "facepalm")} + src={facepalm} + alt="" + /> + <span onMouseUp={(e) => sendReact(e, "👍")}>👍️</span> + <span onMouseUp={(e) => sendReact(e, "👎")}>👎️</span> + <span onMouseUp={(e) => sendReact(e, "☝")}>☝️</span> + <span onMouseUp={(e) => sendReact(e, "🤝")}>🤝</span>️ + <span onMouseUp={(e) => sendReact(e, "🙏")}>🙏</span> + <span onMouseUp={(e) => sendReact(e, "🤡")}>🤡</span> + <span onMouseUp={(e) => sendReact(e, "👀")}>👀</span> + <span onMouseUp={(e) => sendReact(e, "🎤")}>🎤</span> + <span onMouseUp={(e) => sendReact(e, "💯")}>💯</span> + <span onMouseUp={(e) => sendReact(e, "🔥")}>🔥</span> + <img onMouseUp={(e) => sendReact(e, "yeschad")} src={yeschad} alt="" /> + <img + onMouseUp={(e) => sendReact(e, "gigachad")} + src={gigachad} + alt="" + /> + <img onMouseUp={(e) => sendReact(e, "pika")} src={pika} alt="" /> + <img onMouseUp={(e) => sendReact(e, "cringe")} src={cringe} alt="" /> + <img onMouseUp={(e) => sendReact(e, "pepegmi")} src={pepegmi} alt="" /> + <img onMouseUp={(e) => sendReact(e, "pepesad")} src={pepesad} alt="" /> + <img onMouseUp={(e) => sendReact(e, "galaxy")} src={galaxy} alt="" /> + <img onMouseUp={(e) => sendReact(e, "pink")} src={pink} alt="" /> + <img onMouseUp={(e) => sendReact(e, "soy")} src={soy} alt="" /> + <img onMouseUp={(e) => sendReact(e, "cry")} src={cry} alt="" /> + <img onMouseUp={(e) => sendReact(e, "doom")} src={doom} alt="" /> + </div> + </Modal> + ); +} + +export function stringToReact(s: string) { + const em = (emojis as Record<string, string>)[s.replace(/\:/g, "")]; + if (s === "yeschad") + return <img className="react-img" src={yeschad} alt="" />; + if (s === "facepalm") + return <img className="react-img" src={facepalm} alt="" />; + if (s === "yes.jpg") + return <img className="react-img" src={yeschad} alt="" />; + if (s === "gigachad") + return <img className="react-img" src={gigachad} alt="" />; + if (s === "pepechin") + return <img className="react-img" src={pepechin} alt="" />; + if (s === "pepeeyes") + return <img className="react-img" src={pepeeyes} alt="" />; + if (s === "pepegmi") + return <img className="react-img" src={pepegmi} alt="" />; + if (s === "pepesad") + return <img className="react-img" src={pepesad} alt="" />; + if (s === "") + return <img className="react-img no-react" src={emoji} alt="" />; + if (s === "cringe") return <img className="react-img" src={cringe} alt="" />; + if (s === "cry") return <img className="react-img" src={cry} alt="" />; + if (s === "crywojak") return <img className="react-img" src={cry} alt="" />; + if (s === "doom") return <img className="react-img" src={doom} alt="" />; + if (s === "galaxy") return <img className="react-img" src={galaxy} alt="" />; + if (s === "pink") return <img className="react-img" src={pink} alt="" />; + if (s === "pinkwojak") return <img className="react-img" src={pink} alt="" />; + if (s === "soy") return <img className="react-img" src={soy} alt="" />; + if (s === "chad") return <img className="react-img" src={chad} alt="" />; + if (s === "pika") return <img className="react-img" src={pika} alt="" />; + if (em) return <span className="react-icon">{em}</span>; + else if (s.length > 2) return <span className="react-icon"></span>; + else return <span className="react-icon">{s}</span>; +} + +export function TrillReactModal({ poast }: { poast: Poast }) { + const { api } = useLocalState(); + async function sendReact(s: string) { + return await api!.addReact(poast.host, poast.id, s); + } + return <ReactModal send={sendReact} />; +} diff --git a/front/src/components/feed/StatsModal.tsx b/front/src/components/feed/StatsModal.tsx new file mode 100644 index 0000000..4720b2a --- /dev/null +++ b/front/src/components/feed/StatsModal.tsx @@ -0,0 +1,106 @@ +import type { Poast } from "@/types/trill"; +import Modal from "../modals/Modal"; +import { useState } from "react"; +import Post from "./Post"; +import RP from "./RP"; +import Avatar from "../Avatar"; +import { stringToReact } from "./Reactions"; + +function StatsModal({ poast, close }: { close: any; poast: Poast }) { + const [tab, setTab] = useState("replies"); + const replies = poast.children || []; + const quotes = poast.engagement.quoted; + const reposts = poast.engagement.shared; + const reacts = poast.engagement.reacts; + function set(e: React.MouseEvent, s: string) { + e.stopPropagation(); + setTab(s); + } + // TODO revise the global thingy here + return ( + <Modal close={close}> + <div id="stats-modal"> + <Post poast={poast} refetch={() => {}} /> + <div id="tabs"> + <div + role="link" + className={"tab" + (tab === "replies" ? " active-tab" : "")} + onClick={(e) => set(e, "replies")} + > + <h4>Replies</h4> + </div> + <div + role="link" + className={"tab" + (tab === "quotes" ? " active-tab" : "")} + onClick={(e) => set(e, "quotes")} + > + <h4>Quotes</h4> + </div> + <div + role="link" + className={"tab" + (tab === "reposts" ? " active-tab" : "")} + onClick={(e) => set(e, "reposts")} + > + <h4>Reposts</h4> + </div> + <div + role="link" + className={"tab" + (tab === "reacts" ? " active-tab" : "")} + onClick={(e) => set(e, "reacts")} + > + <h4>Reacts</h4> + </div> + </div> + <div id="engagement"> + {tab === "replies" ? ( + <div id="replies"> + {replies.map((p) => ( + <div key={p} className="reply-stat"> + <RP + host={poast.host} + id={p} + rter={undefined} + rtat={undefined} + rtid={undefined} + /> + </div> + ))} + </div> + ) : tab === "quotes" ? ( + <div id="quotes"> + {quotes.map((p) => ( + <div key={p.pid.id} className="quote-stat"> + <RP + host={p.pid.ship} + id={p.pid.id} + rter={undefined} + rtat={undefined} + rtid={undefined} + /> + </div> + ))} + </div> + ) : tab === "reposts" ? ( + <div id="reposts"> + {reposts.map((p) => ( + <div key={p.pid.id} className="repost-stat"> + <Avatar p={p.pid.ship} size={40} /> + </div> + ))} + </div> + ) : tab === "reacts" ? ( + <div id="reacts"> + {Object.keys(reacts).map((p) => ( + <div key={p} className="react-stat btw"> + <Avatar p={p} size={32} /> + {stringToReact(reacts[p])} + </div> + ))} + </div> + ) : null} + </div> + </div> + </Modal> + ); +} +export default StatsModal; diff --git a/front/src/components/layout/Sidebar.tsx b/front/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..1568421 --- /dev/null +++ b/front/src/components/layout/Sidebar.tsx @@ -0,0 +1,81 @@ +import { RADIO, versionNum } from "@/logic/constants"; +import { useLocation } from "wouter"; +import useLocalState from "@/state/state"; +import key from "@/assets/icons/key.svg"; +import logo from "@/assets/icons/logo.png"; +import home from "@/assets/icons/home.svg"; +import bell from "@/assets/icons/bell.svg"; +import settings from "@/assets/icons/settings.svg"; +import messages from "@/assets/icons/messages.svg"; +import profile from "@/assets/icons/profile.svg"; +import pals from "@/assets/icons/pals.svg"; +import rumors from "@/assets/icons/rumors.svg"; +import { ThemeSwitcher } from "@/styles/ThemeSwitcher"; + +function SlidingMenu() { + const [_, navigate] = useLocation(); + const { api } = useLocalState(); + function goto(to: string) { + navigate(to); + } + return ( + <div id="left-menu"> + <div id="logo"> + <img src={logo} /> + <h3> Nostril </h3> + </div> + <h3>Feeds</h3> + <div className="opt" role="link" onClick={() => goto(`/feed/global`)}> + <img src={home} alt="" /> + <div>Home</div> + </div> + <div className="opt" role="link" onClick={() => goto(`/hark`)}> + <img src={bell} alt="" /> + <div>Activity</div> + </div> + <hr /> + + <div className="opt" role="link" onClick={() => goto("/chat")}> + <img src={messages} alt="" /> + <div>Messages</div> + </div> + <div className="opt" role="link" onClick={() => goto("/pals")}> + <img src={pals} alt="" /> + <div>Pals</div> + </div> + <hr /> + <div + className="opt" + role="link" + onClick={() => goto(`/feed/${api!.airlock.our}`)} + > + <img src={profile} alt="" /> + <div>Profile</div> + </div> + <div className="opt" role="link" onClick={() => goto("/feed/anon")}> + <img src={rumors} alt="" /> + <div>Rumors</div> + </div> + <hr /> + <div className="opt" role="link" onClick={() => goto("/radio")}> + <div className="img">{RADIO}</div> + <div>Radio</div> + </div> + <hr /> + <div + className="opt" + role="link" + onClick={() => (window.location.href = "/cookies")} + > + <img src={key} alt="" /> + <div>Logins</div> + </div> + <div className="opt" role="link" onClick={() => goto("/sets")}> + <img src={settings} alt="" /> + <div>Settings</div> + </div> + <ThemeSwitcher /> + </div> + ); +} +export default SlidingMenu; diff --git a/front/src/components/modals/Modal.tsx b/front/src/components/modals/Modal.tsx new file mode 100644 index 0000000..7dd688c --- /dev/null +++ b/front/src/components/modals/Modal.tsx @@ -0,0 +1,72 @@ +import useLocalState from "@/state/state"; +import { useEffect, useRef, useState } from "react"; + +function Modal({ children }: any) { + const { setModal } = useLocalState(); + function onKey(event: any) { + if (event.key === "Escape") setModal(null); + } + useEffect(() => { + document.addEventListener("keyup", onKey); + return () => { + document.removeEventListener("keyup", onKey); + }; + }, [children]); + + function clickAway(e: React.MouseEvent) { + console.log("clicked away"); + e.stopPropagation(); + if (!modalRef.current || !modalRef.current.contains(e.target)) + setModal(null); + } + const modalRef = useRef(null); + return ( + <div id="modal-background" onClick={clickAway}> + <div id="modal" ref={modalRef}> + {children} + </div> + </div> + ); +} +export default Modal; + +export function Welcome() { + return ( + <Modal> + <div id="welcome-msg"> + <h1>Welcome to Nostril!</h1> + <p> + Trill is the world's only truly free and sovereign social media + platform, powered by Urbit. + </p> + <p> + Click on the crow icon on the top left to see all available feeds. + </p> + <p>The Global feed should be populated by default.</p> + <p>Follow people soon so your Global feed doesn't go stale.</p> + <p> + Trill is still on beta. The UI is Mobile only, we recommend you use + your phone or the browser dev tools. Desktop UI is on the works. + </p> + <p> + If you have any feedback please reach out to us on Groups at + ~hoster-dozzod-sortug/trill or here at ~polwex + </p> + </div> + </Modal> + ); +} + +export function Tooltip({ children, text, className }: any) { + const [show, toggle] = useState(false); + return ( + <div + className={"tooltip-wrapper " + (className || "")} + onMouseOver={() => toggle(true)} + onMouseOut={() => toggle(false)} + > + {children} + {show && <div className="tooltip">{text}</div>} + </div> + ); +} diff --git a/front/src/components/modals/ShipModal.tsx b/front/src/components/modals/ShipModal.tsx new file mode 100644 index 0000000..86bffbb --- /dev/null +++ b/front/src/components/modals/ShipModal.tsx @@ -0,0 +1,45 @@ +import type { Ship } from "@/types/urbit"; +import Modal from "./Modal"; +import Avatar from "../Avatar"; +import copyIcon from "@/assets/icons/copy.svg"; +import useLocalState from "@/state/state"; +import { useLocation } from "wouter"; +import toast from "react-hot-toast"; + +export default function ({ ship }: { ship: Ship }) { + const { setModal, api } = useLocalState(); + const [_, navigate] = useLocation(); + function close() { + setModal(null); + } + async function copy(e: React.MouseEvent) { + e.stopPropagation(); + await navigator.clipboard.writeText(ship); + toast.success("Copied to clipboard"); + } + return ( + <Modal close={close}> + <div id="ship-modal"> + <div className="flex"> + <Avatar p={ship} size={60} /> + <img + className="copy-icon cp" + role="link" + onClick={copy} + src={copyIcon} + alt="" + /> + </div> + <div className="buttons f1"> + <button onClick={() => navigate(`/feed/${ship}`)}>Feed</button> + <button onClick={() => navigate(`/pals/${ship}`)}>Profile</button> + {ship !== api!.airlock.our && ( + <> + <button onClick={() => navigate(`/chat/dm/${ship}`)}>DM</button> + </> + )} + </div> + </div> + </Modal> + ); +} diff --git a/front/src/components/snippets/Snippets.tsx b/front/src/components/snippets/Snippets.tsx new file mode 100644 index 0000000..68f5446 --- /dev/null +++ b/front/src/components/snippets/Snippets.tsx @@ -0,0 +1,395 @@ +import { fetchTweet, lurkTweet } from "@/logic/twatter/calls"; +import { pokeDister, scryDister, scryGangs } from "@/logic/requests/tlon"; +import { useEffect, useState } from "react"; +import Tweet from "@/sections/twatter/Tweet"; +import { toFlat } from "@/sections/feed/thread/helpers"; +import PostData from "@/sections/feed/PostData"; +import Post from "@/sections/feed/post/Post"; +import { FullNode, SortugRef } from "@/types/trill"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { subscribe, unsub } from "@/logic/requests/generic"; +import { AppData, GroupMetadata } from "@/types/tlon"; +import comet from "@/assets/icons/comet.svg"; +import Sigil from "@/ui/Sigil"; +import { PollLoader } from "@/sections/feed/poll/Show"; +import { parseThread, parseTweet } from "@/logic/twatter/parser"; +import { Tweet as TweetType } from "@/types/twatter"; +import { scryRadio } from "@/logic/requests/nostril"; +import useLocalState from "@/state/state"; +import { RadioTower, ScheduledRadio, radioLink } from "@/logic/requests/radio"; +import { Ship } from "@/types/urbit"; +import { RADIO } from "@/logic/constants"; +import { SigilOnly } from "../Avatar"; +import { date_diff } from "@/logic/utils"; +import ShipsModal from "../modals/ShipsModal"; + +export function TrillSnippet({ r }: { r: SortugRef }) { + const { ship, path } = r; + return PostData({ host: ship, id: path.slice(1) })(TrillSnippetMarkup); +} +function TrillSnippetMarkup({ + data, + refetch, +}: { + data: FullNode; + refetch: Function; +}) { + return ( + <div className="trill-snippet"> + <Post poast={toFlat(data)} refetch={refetch} /> + </div> + ); +} +// <div +// onClick={() => { +// if (pop) pop(link); +// }} +// className="chat-snippet trill-snippet" +// > +// Post not found +// </div> +// ); + +export function TweetSnippet({ + link, + giveBack, +}: { + link: string; + giveBack?: Function; +}) { + const id = link.split("/")[5]; + const { isLoading, isError, data } = useQuery({ + queryKey: ["twatter-thread", id], + queryFn: () => lurkTweet(id), + }); + const [tw, setTw] = useState<TweetType>(); + useEffect(() => { + if (data && "thread-lurk" in data) { + const js = JSON.parse(data["thread-lurk"]).data.tweetResult; + if (JSON.stringify(js) === "{}") return; + if (giveBack) giveBack(JSON.stringify(parseTweet(js.result))); + } + }, [data]); + if (isLoading || isError) + return ( + <div className="tweet-snippet"> + <p>Fetching Tweet from your Urbit...</p> + </div> + ); + else { + if ("no-coki" in data) + return ( + <div id="cookie-error" className="x-center"> + <p className="">Your Twitter cookie isn't working correctly.</p> + <a href="/cookies">Check it out</a> + </div> + ); + if ("fail" in data) + return ( + <p> + Bad request. Please send some feedback (here) of what you were trying + to fetch. + </p> + ); + if ("thread-lurk" in data) { + const js = JSON.parse(data["thread-lurk"]).data.tweetResult; + if (JSON.stringify(js) === "{}") + return null; // TODO wtf + else + return ( + <div className="tweet-snippet"> + <Tweet tweet={parseTweet(js.result)} quote={true} /> + </div> + ); + } + // else { + // const head = parseThread(JSON.parse(data.thread)); + // const tweet = head.thread.tweets[0] + // giveBack(JSON.stringify(tweet)) + // return ( + // <div className="tweet-snippet"> + // <Tweet tweet={tweet} quote={true} /> + // </div> + // ); + // } + } +} + +export function AppSnippet({ r }: { r: SortugRef }) { + async function sub() { + if (!subn) { + const s = await subscribe( + "treaty", + "/treaties", + (data: { add: AppData }) => { + if ("ini" in data) { + const app = Object.values(data.ini).find((d) => d.desk === name); + setApp(app); + } + if ("add" in data && data.add.desk === name) setApp(data.add); + if (appData) unsub(subn); + }, + ); + setSub(s); + const res = await pokeDister(ship); + } + } + const { ship, path } = r; + const name = path.slice(1); + const [appData, setApp] = useState<AppData>(); + const [subn, setSub] = useState<number>(); + const { isLoading, data, isError } = useQuery({ + queryKey: ["dister", ship], + queryFn: () => scryDister(ship), + }); + if (isLoading || isError) return <div className="reference">...</div>; + else { + const app = Object.values(data.ini).find((d) => d.desk === name); + if (!app && !appData) sub(); + const a = app + ? app + : appData + ? appData + : { title: name, image: comet, info: "", ship }; + return ( + <div className="reference app-ref"> + <AppDiv app={a} /> + </div> + ); + } +} +function AppDiv({ app }: { app: Partial<AppData> }) { + return ( + <> + <img src={app.image} alt="" /> + <div className="text"> + <p className="app-name">{app.title}</p> + <p className="app-info">{app.info}</p> + <p className="app-host">App from {app.ship}</p> + </div> + <p className="ref-ship"> + <Sigil patp={app.ship} size={40} /> + </p> + </> + ); +} + +export function TlonSnippet({ r }: { r: SortugRef }) { + if (r.type === "app") return <AppSnippet r={r} />; + if (r.type === "groups") return <GroupSnippet r={r} />; +} +export function GroupSnippet({ r }: { r: SortugRef }) { + const queryClient = useQueryClient(); + async function sub() { + if (!subn) { + const path = `/gangs/index/${ship}`; + const s = await subscribe("groups", path, (data: any) => { + const key = `${ship}/${name}`; + const val = data[key]; + queryClient.setQueryData(["gangs"], (old: any) => { + return { ...old, [key]: { preview: val } }; + }); + }); + setSub(s); + } + } + const { ship, path } = r; + const name = path.slice(1); + const [groupData, setGroup] = useState<GroupMetadata>(); + const [subn, setSub] = useState<number>(); + const { isLoading, data, isError } = useQuery({ + queryKey: ["gangs"], + queryFn: scryGangs, + }); + if (isLoading || isError) return <div className="reference">...</div>; + else { + const group = data[`${ship}/${name}`]; + if (!group && !groupData) sub(); + const a = + group && group.preview + ? group.preview.meta + : groupData + ? groupData + : { title: name, image: comet, cover: "", description: "" }; + return ( + <div className="reference app-ref"> + {a.image.startsWith("#") ? ( + <div + className="group-color" + style={{ backgroundColor: a.image }} + ></div> + ) : ( + <img src={a.image} alt="" /> + )} + <div className="text"> + <p className="app-name">{a.title}</p> + <p className="app-info"> + {a.description.length > 25 + ? a.description.substring(0, 25) + "..." + : a.description} + </p> + <p className="group-host">Group by {ship}</p> + </div> + {/* <p className="ref-ship"> + <Sigil patp={ship} size={40} /> + </p> */} + </div> + ); + } +} + +export function PollSnippet({ r }: { r: SortugRef }) { + return ( + <div className="poll-snippet"> + <PollLoader ship={r.ship} id={r.path.slice(1)} /> + </div> + ); +} + +export function SnippetHandler(props: { r: SortugRef }) { + if (props.r.type === "trill") return <TrillSnippet r={props.r} />; + if (props.r.type === "trill-polls") return <PollSnippet r={props.r} />; + if (props.r.type === "app") return <AppSnippet r={props.r} />; + if (props.r.type === "groups") return <GroupSnippet r={props.r} />; +} + +export function RadioSnippet({ ship }: { ship: Ship }) { + const { our } = useLocalState(); + return ship === our ? <OwnRadio /> : <DudesRadio ship={ship} />; +} + +function DudesRadio({ ship }: { ship }) { + function onc() { + radioLink(ship); + } + const { radioTowers } = useLocalState(); + const tower = radioTowers.find((t) => t.location === ship); + if (!tower) + return ( + <div role="link" onMouseUp={onc} className="radio-snippet"> + <p className="img">{RADIO}</p> + <div className="radio-text"> + <p>Radio data not published. Click and check.</p>; + </div> + </div> + ); + else + return ( + <div role="link" onMouseUp={onc} className="radio-snippet"> + <p className="img">{RADIO}</p> + <div className="radio-text"> + <p>Radio Session. Playing: {tower.description}</p> + <p>Started {new Date(tower.time).toLocaleString()}</p> + </div> + <div> + <SigilOnly p={ship} size={42} /> + <span className="viewers"> + {tower.viewers} + <span>👀</span> + </span> + </div> + </div> + ); +} + +function OwnRadio() { + const { currentRadio, our, setModal, radioTowers } = useLocalState(); + const [scheduled, setS] = useState<ScheduledRadio | null>(null); + function onc() { + radioLink(our); + } + useEffect(() => { + scryRadio().then((r) => { + if (r) setS(r.radio); + }); + }, []); + function showViewers() { + const modal = ( + <ShipsModal + ships={currentRadio.viewers} + header={`People watching your %radio show`} + /> + ); + setModal(modal); + } + if (scheduled && scheduled.time > Date.now()) + return ( + <div role="link" onMouseUp={onc} className="radio-snippet"> + <p className="img">{RADIO}</p> + <div className="radio-text"> + <p> + Radio Session. Playing: + <a className="radio-link" href={scheduled.url}> + {scheduled.desc} + </a> + </p> + <p>Starting at {new Date(scheduled.time).toLocaleString()}</p> + </div> + <div> + <SigilOnly p={our} size={42} /> + </div> + </div> + ); + else if (!currentRadio) + return ( + <div role="link" onMouseUp={onc} className="radio-snippet"> + <p className="img">{RADIO}</p> + <div className="radio-text"> + <p>Radio unavailable</p> + </div> + </div> + ); + else + return ( + <div role="link" onMouseUp={onc} className="radio-snippet"> + <p className="img">{RADIO}</p> + <div className="radio-text"> + <p> + Radio Session. Playing: + <a className="radio-link" href={currentRadio.stream}> + {currentRadio.description} + </a> + </p> + {/* <p>Started {date_diff(currentRadio.time, "long")}</p> */} + </div> + <div> + <SigilOnly p={our} size={42} /> + <span onClick={showViewers} className="viewers"> + {currentRadio?.viewers?.length || ""} + <span>👀</span> + </span> + </div> + </div> + ); + + // return ( + // {scheduled > Date.now() + // ? (<> + // <p> + // Radio Session. Playing: + // <a className="radio-link" target="_blank" href={currentRadio.stream}> + // {currentRadio.description} + // </a> + // </p> + + // <p>Starting at {new Date(scheduled).toLocaleString()}</p> + // </> + + // ): scheduled !== 0() + + // } + // <p> + // Radio Session. Playing: + // <a className="radio-link" target="_blank" href={currentRadio.stream}> + // {currentRadio.description} + // </a> + // </p> + // {scheduled && scheduled > Date.now() ? ( + // <p>Starting at {new Date(scheduled).toLocaleString()}</p> + // ) : scheduled !== 0 ? ( + // <p>Started {date_diff(new Date(scheduled), "long")}. Click to join.</p> + // ) : ( + // <p>Unscheduled session. Click to join.</p> + // )} + // ); +} diff --git a/front/src/logic/api.ts b/front/src/logic/api.ts new file mode 100644 index 0000000..b8acba2 --- /dev/null +++ b/front/src/logic/api.ts @@ -0,0 +1,15 @@ +import Urbit from "urbit-api"; + +export const URL = import.meta.env.PROD ? "" : "http://localhost:8080"; + +export async function start(): Promise<Urbit> { + const airlock = new Urbit(URL, ""); + const res = await fetch(URL + "/~/host"); + const ship = await res.text(); + airlock.ship = ship.slice(1); + airlock.our = ship; + airlock.desk = "nostril"; + await airlock.poke({ app: "hood", mark: "helm-hi", json: "opening airlock" }); + await airlock.eventSource(); + return airlock; +} diff --git a/front/src/logic/bunts.ts b/front/src/logic/bunts.ts new file mode 100644 index 0000000..dfa70e3 --- /dev/null +++ b/front/src/logic/bunts.ts @@ -0,0 +1,51 @@ +import type { Engagement, List, Lock } from "@/types/trill"; + +export const openLock: Lock = { + rank: { caveats: [], locked: false, public: true }, + luk: { caveats: [], locked: false, public: true }, + ship: { caveats: [], locked: false, public: true }, + tags: { caveats: [], locked: false, public: true }, + custom: { fn: null, public: false }, +}; + +export const engagementBunt: Engagement = { + reacts: {}, + quoted: [], + shared: [], +}; + +export const pushStateBunt = { + followers: [], + gate: { + lock: openLock, + mute: openLock, + begs: [], + "post-begs": [], + backlog: 0, + }, +}; + +export const harkStateBunt = { + unread: {}, + engagement: [], +}; + +export const pullStateBunt = { + following: [], + begs: [], + "post-begs": [], +}; +export const listBunt: List = { + symbol: "", + name: "", + desc: "", + icon: "", + cover: "", + members: [], + public: true, +}; + +// export const palsBunt: Pals = { +// incoming: {}, +// outgoing: {} +// } diff --git a/front/src/logic/constants.ts b/front/src/logic/constants.ts new file mode 100644 index 0000000..fcf5573 --- /dev/null +++ b/front/src/logic/constants.ts @@ -0,0 +1,36 @@ +import type { Poast } from "@/types/trill"; + +export const versionNum = "0.1.0"; +export const TIMEOUT = 15_000; + +export const ChatPostCount = 50; +export const FeedPostCount = 50; +export const RumorShip = "~londev-dozzod-sortug"; +export const RumorShip2 = "~paldev"; + +export function isRumor(poast: Poast) { + return poast.author === RumorShip || poast.author === RumorShip2; +} + +export const MOBILE_BROWSER_REGEX = + /Android|webOS|iPhone|iPad|iPod|BlackBerry/i; +export const AUDIO_REGEX = new RegExp(/https:\/\/.+\.(mp3|wav|ogg)\b/gim); +export const VIDEO_REGEX = new RegExp(/https:\/\/.+\.(mov|mp4|ogv)\b/gim); +export const TWITTER_REGEX = new RegExp( + /https:\/\/(twitter|x)\.com\/.+\/status\/\d+/gim, +); + +export const REF_REGEX = new RegExp( + /urbit:\/\/[a-z0-9-]+\/~[a-z-_]+\/[a-z0-9-_]+/gim, +); +export const RADIO_REGEX = new RegExp(/urbit:\/\/radio\/~[a-z-_]+/gim); + +export const IMAGE_REGEX = new RegExp( + /https:\/\/.+\.(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)\b/gim, +); + +export const SHIP_REGEX = new RegExp(/\B~[a-z-]+/); +export const HASHTAGS_REGEX = new RegExp(/#[a-z-]+/g); + +export const DEFAULT_DATE = { year: 1970, month: 1, day: 1 }; +export const RADIO = "📻"; diff --git a/front/src/logic/emojis.json b/front/src/logic/emojis.json new file mode 100644 index 0000000..599f707 --- /dev/null +++ b/front/src/logic/emojis.json @@ -0,0 +1,3613 @@ +{ + "100": "💯", + "1000": "1000", + "1234": "🔢", + "white_heavy_check_mark": "✅", + "done": "✅", + "pants": "👖", + "squirrel": "🐿️", + "partyparrot": "🦜", + "party-parrot": "🦜", + "60fps_parrot": "🦜", + "charmander": "🔥", + "java": "☕", + "slack": "S", + "discord": "D", + "github": "G", + "jira": "J", + "shame": "😳", + "airbyte-100": "💯", + "airbyte-fire": "🔥", + "firee": "🔥", + "meow_party": "🎉", + "meowparty": "🎉", + "partyblob": "🎉", + "party-blob": "🎉", + "party_blob": "🎉", + "octavia-hmm": "🤔", + "octavia-hello": "👋", + "ory_love": "❤️", + "grinning": "😀", + "smiley": "😃", + "smile": "😄", + "grin": "😁", + "laughing": "😆", + "satisfied": "😆", + "sweat_smile": "😅", + "rolling_on_the_floor_laughing": "🤣", + "joy": "😂", + "slightly_smiling_face": "🙂", + "upside_down_face": "🙃", + "melting_face": "🫠", + "wink": "😉", + "blush": "😊", + "innocent": "😇", + "smiling_face_with_3_hearts": "🥰", + "heart_eyes": "😍", + "star-struck": "🤩", + "grinning_face_with_star_eyes": "🤩", + "kissing_heart": "😘", + "kissing": "😗", + "relaxed": "☺️", + "kissing_closed_eyes": "😚", + "kissing_smiling_eyes": "😙", + "smiling_face_with_tear": "🥲", + "yum": "😋", + "stuck_out_tongue": "😛", + "stuck_out_tongue_winking_eye": "😜", + "zany_face": "🤪", + "grinning_face_with_one_large_and_one_small_eye": "🤪", + "stuck_out_tongue_closed_eyes": "😝", + "money_mouth_face": "🤑", + "hugging_face": "🤗", + "face_with_hand_over_mouth": "🤭", + "smiling_face_with_smiling_eyes_and_hand_covering_mouth": "🤭", + "face_with_open_eyes_and_hand_over_mouth": "🫢", + "face_with_peeking_eye": "🫣", + "shushing_face": "🤫", + "face_with_finger_covering_closed_lips": "🤫", + "thinking_face": "🤔", + "saluting_face": "🫡", + "zipper_mouth_face": "🤐", + "face_with_raised_eyebrow": "🤨", + "face_with_one_eyebrow_raised": "🤨", + "neutral_face": "😐", + "expressionless": "😑", + "no_mouth": "😶", + "dotted_line_face": "🫥", + "face_in_clouds": "😶🌫️", + "smirk": "😏", + "unamused": "😒", + "face_with_rolling_eyes": "🙄", + "grimacing": "😬", + "face_exhaling": "😮💨", + "lying_face": "🤥", + "relieved": "😌", + "pensive": "😔", + "sleepy": "😪", + "drooling_face": "🤤", + "sleeping": "😴", + "mask": "😷", + "face_with_thermometer": "🤒", + "face_with_head_bandage": "🤕", + "nauseated_face": "🤢", + "face_vomiting": "🤮", + "face_with_open_mouth_vomiting": "🤮", + "sneezing_face": "🤧", + "hot_face": "🥵", + "cold_face": "🥶", + "woozy_face": "🥴", + "dizzy_face": "😵", + "face_with_spiral_eyes": "😵💫", + "exploding_head": "🤯", + "wow_fb": "🤯", + "shocked_face_with_exploding_head": "🤯", + "face_with_cowboy_hat": "🤠", + "partying_face": "🥳", + "disguised_face": "🥸", + "sunglasses": "😎", + "nerd_face": "🤓", + "face_with_monocle": "🧐", + "confused": "😕", + "face_with_diagonal_mouth": "🫤", + "worried": "😟", + "slightly_frowning_face": "🙁", + "white_frowning_face": "☹️", + "open_mouth": "😮", + "hushed": "😯", + "astonished": "😲", + "flushed": "😳", + "pleading_face": "🥺", + "face_holding_back_tears": "🥹", + "frowning": "😦", + "anguished": "😧", + "fearful": "😨", + "cold_sweat": "😰", + "disappointed_relieved": "😥", + "cry": "😢", + "sob": "😭", + "scream": "😱", + "confounded": "😖", + "persevere": "😣", + "disappointed": "😞", + "sweat": "😓", + "weary": "😩", + "tired_face": "😫", + "yawning_face": "🥱", + "triumph": "😤", + "rage": "😡", + "jakesidsmithmadness": "😡", + "angry": "😠", + "face_with_symbols_on_mouth": "🤬", + "serious_face_with_symbols_covering_mouth": "🤬", + "smiling_imp": "😈", + "imp": "👿", + "skull": "💀", + "skull_and_crossbones": "☠️", + "hankey": "💩", + "shit": "💩", + "clown_face": "🤡", + "japanese_ogre": "👹", + "japanese_goblin": "👺", + "ghost": "👻", + "alien": "👽", + "space_invader": "👾", + "robot_face": "🤖", + "android": "🤖", + "smiley_cat": "😺", + "smile_cat": "😸", + "joy_cat": "😹", + "heart_eyes_cat": "😻", + "smirk_cat": "😼", + "kissing_cat": "😽", + "scream_cat": "🙀", + "crying_cat_face": "😿", + "pouting_cat": "😾", + "see_no_evil": "🙈", + "hear_no_evil": "🙉", + "speak_no_evil": "🙊", + "kiss": "💋", + "love_letter": "💌", + "cupid": "💘", + "gift_heart": "💝", + "sparkling_heart": "💖", + "heartpulse": "💗", + "heartbeat": "💓", + "revolving_hearts": "💞", + "two_hearts": "💕", + "heart_decoration": "💟", + "heavy_heart_exclamation_mark_ornament": "❣️", + "broken_heart": "💔", + "heart_on_fire": "❤️🔥", + "mending_heart": "❤️🩹", + "heart": "❤️", + "kodee_love": "❤️", + "kodee-love": "❤️", + "orange_heart": "🧡", + "yellow_heart": "💛", + "green_heart": "💚", + "blue_heart": "💙", + "purple_heart": "💜", + "brown_heart": "🤎", + "black_heart": "🖤", + "white_heart": "🤍", + "anger": "💢", + "boom": "💥", + "collision": "💥", + "dizzy": "💫", + "sweat_drops": "💦", + "dash": "💨", + "hole": "🕳️", + "bomb": "💣", + "speech_balloon": "💬", + "eye-in-speech-bubble": "👁️🗨️", + "left_speech_bubble": "🗨️", + "right_anger_bubble": "🗯️", + "thought_balloon": "💭", + "zzz": "💤", + "wave_animated": "👋", + "wave": "👋", + "wave::skin-tone-2": "👋🏻", + "wave::skin-tone-3": "👋🏼", + "wave::skin-tone-4": "👋🏽", + "wave::skin-tone-5": "👋🏾", + "wave::skin-tone-6": "👋🏿", + "raised_back_of_hand": "🤚", + "raised_back_of_hand::skin-tone-2": "🤚🏻", + "raised_back_of_hand::skin-tone-3": "🤚🏼", + "raised_back_of_hand::skin-tone-4": "🤚🏽", + "raised_back_of_hand::skin-tone-5": "🤚🏾", + "raised_back_of_hand::skin-tone-6": "🤚🏿", + "raised_hand_with_fingers_splayed": "🖐️", + "raised_hand_with_fingers_splayed::skin-tone-2": "🖐🏻", + "raised_hand_with_fingers_splayed::skin-tone-3": "🖐🏼", + "raised_hand_with_fingers_splayed::skin-tone-4": "🖐🏽", + "raised_hand_with_fingers_splayed::skin-tone-5": "🖐🏾", + "raised_hand_with_fingers_splayed::skin-tone-6": "🖐🏿", + "hand": "✋", + "raised_hand": "✋", + "hand::skin-tone-2": "✋🏻", + "raised_hand::skin-tone-2": "✋🏻", + "hand::skin-tone-3": "✋🏼", + "raised_hand::skin-tone-3": "✋🏼", + "hand::skin-tone-4": "✋🏽", + "raised_hand::skin-tone-4": "✋🏽", + "hand::skin-tone-5": "✋🏾", + "raised_hand::skin-tone-5": "✋🏾", + "hand::skin-tone-6": "✋🏿", + "raised_hand::skin-tone-6": "✋🏿", + "spock-hand": "🖖", + "spock-hand::skin-tone-2": "🖖🏻", + "spock-hand::skin-tone-3": "🖖🏼", + "spock-hand::skin-tone-4": "🖖🏽", + "spock-hand::skin-tone-5": "🖖🏾", + "spock-hand::skin-tone-6": "🖖🏿", + "rightwards_hand": "🫱", + "rightwards_hand::skin-tone-2": "🫱🏻", + "rightwards_hand::skin-tone-3": "🫱🏼", + "rightwards_hand::skin-tone-4": "🫱🏽", + "rightwards_hand::skin-tone-5": "🫱🏾", + "rightwards_hand::skin-tone-6": "🫱🏿", + "leftwards_hand": "🫲", + "leftwards_hand::skin-tone-2": "🫲🏻", + "leftwards_hand::skin-tone-3": "🫲🏼", + "leftwards_hand::skin-tone-4": "🫲🏽", + "leftwards_hand::skin-tone-5": "🫲🏾", + "leftwards_hand::skin-tone-6": "🫲🏿", + "palm_down_hand": "🫳", + "palm_down_hand::skin-tone-2": "🫳🏻", + "palm_down_hand::skin-tone-3": "🫳🏼", + "palm_down_hand::skin-tone-4": "🫳🏽", + "palm_down_hand::skin-tone-5": "🫳🏾", + "palm_down_hand::skin-tone-6": "🫳🏿", + "palm_up_hand": "🫴", + "palm_up_hand::skin-tone-2": "🫴🏻", + "palm_up_hand::skin-tone-3": "🫴🏼", + "palm_up_hand::skin-tone-4": "🫴🏽", + "palm_up_hand::skin-tone-5": "🫴🏾", + "palm_up_hand::skin-tone-6": "🫴🏿", + "ok_hand": "👌", + "ok_hand::skin-tone-2": "👌🏻", + "ok_hand::skin-tone-3": "👌🏼", + "ok_hand::skin-tone-4": "👌🏽", + "ok_hand::skin-tone-5": "👌🏾", + "ok_hand::skin-tone-6": "👌🏿", + "nice": "👌", + "nice::skin-tone-2": "👌🏻", + "nice::skin-tone-3": "👌🏼", + "nice::skin-tone-4": "👌🏽", + "nice::skin-tone-5": "👌🏾", + "nice::skin-tone-6": "👌🏿", + "pinched_fingers": "🤌", + "pinched_fingers::skin-tone-2": "🤌🏻", + "pinched_fingers::skin-tone-3": "🤌🏼", + "pinched_fingers::skin-tone-4": "🤌🏽", + "pinched_fingers::skin-tone-5": "🤌🏾", + "pinched_fingers::skin-tone-6": "🤌🏿", + "pinching_hand": "🤏", + "pinching_hand::skin-tone-2": "🤏🏻", + "pinching_hand::skin-tone-3": "🤏🏼", + "pinching_hand::skin-tone-4": "🤏🏽", + "pinching_hand::skin-tone-5": "🤏🏾", + "pinching_hand::skin-tone-6": "🤏🏿", + "v": "✌️", + "v::skin-tone-2": "✌🏻", + "v::skin-tone-3": "✌🏼", + "v::skin-tone-4": "✌🏽", + "v::skin-tone-5": "✌🏾", + "v::skin-tone-6": "✌🏿", + "crossed_fingers": "🤞", + "hand_with_index_and_middle_fingers_crossed": "🤞", + "crossed_fingers::skin-tone-2": "🤞🏻", + "hand_with_index_and_middle_fingers_crossed::skin-tone-2": "🤞🏻", + "crossed_fingers::skin-tone-3": "🤞🏼", + "hand_with_index_and_middle_fingers_crossed::skin-tone-3": "🤞🏼", + "crossed_fingers::skin-tone-4": "🤞🏽", + "hand_with_index_and_middle_fingers_crossed::skin-tone-4": "🤞🏽", + "crossed_fingers::skin-tone-5": "🤞🏾", + "hand_with_index_and_middle_fingers_crossed::skin-tone-5": "🤞🏾", + "crossed_fingers::skin-tone-6": "🤞🏿", + "hand_with_index_and_middle_fingers_crossed::skin-tone-6": "🤞🏿", + "hand_with_index_finger_and_thumb_crossed": "🫰", + "hand_with_index_finger_and_thumb_crossed::skin-tone-2": "🫰🏻", + "hand_with_index_finger_and_thumb_crossed::skin-tone-3": "🫰🏼", + "hand_with_index_finger_and_thumb_crossed::skin-tone-4": "🫰🏽", + "hand_with_index_finger_and_thumb_crossed::skin-tone-5": "🫰🏾", + "hand_with_index_finger_and_thumb_crossed::skin-tone-6": "🫰🏿", + "i_love_you_hand_sign": "🤟", + "i_love_you_hand_sign::skin-tone-2": "🤟🏻", + "i_love_you_hand_sign::skin-tone-3": "🤟🏼", + "i_love_you_hand_sign::skin-tone-4": "🤟🏽", + "i_love_you_hand_sign::skin-tone-5": "🤟🏾", + "i_love_you_hand_sign::skin-tone-6": "🤟🏿", + "the_horns": "🤘", + "sign_of_the_horns": "🤘", + "the_horns::skin-tone-2": "🤘🏻", + "sign_of_the_horns::skin-tone-2": "🤘🏻", + "the_horns::skin-tone-3": "🤘🏼", + "sign_of_the_horns::skin-tone-3": "🤘🏼", + "the_horns::skin-tone-4": "🤘🏽", + "sign_of_the_horns::skin-tone-4": "🤘🏽", + "the_horns::skin-tone-5": "🤘🏾", + "sign_of_the_horns::skin-tone-5": "🤘🏾", + "the_horns::skin-tone-6": "🤘🏿", + "sign_of_the_horns::skin-tone-6": "🤘🏿", + "call_me_hand": "🤙", + "call_me_hand::skin-tone-2": "🤙🏻", + "call_me_hand::skin-tone-3": "🤙🏼", + "call_me_hand::skin-tone-4": "🤙🏽", + "call_me_hand::skin-tone-5": "🤙🏾", + "call_me_hand::skin-tone-6": "🤙🏿", + "point_left": "👈", + "point_left::skin-tone-2": "👈🏻", + "point_left::skin-tone-3": "👈🏼", + "point_left::skin-tone-4": "👈🏽", + "point_left::skin-tone-5": "👈🏾", + "point_left::skin-tone-6": "👈🏿", + "point_right": "👉", + "point_right::skin-tone-2": "👉🏻", + "point_right::skin-tone-3": "👉🏼", + "point_right::skin-tone-4": "👉🏽", + "point_right::skin-tone-5": "👉🏾", + "point_right::skin-tone-6": "👉🏿", + "point_up_2": "👆", + "point_up_2::skin-tone-2": "👆🏻", + "point_up_2::skin-tone-3": "👆🏼", + "point_up_2::skin-tone-4": "👆🏽", + "point_up_2::skin-tone-5": "👆🏾", + "point_up_2::skin-tone-6": "👆🏿", + "middle_finger": "🖕", + "reversed_hand_with_middle_finger_extended": "🖕", + "middle_finger::skin-tone-2": "🖕🏻", + "reversed_hand_with_middle_finger_extended::skin-tone-2": "🖕🏻", + "middle_finger::skin-tone-3": "🖕🏼", + "reversed_hand_with_middle_finger_extended::skin-tone-3": "🖕🏼", + "middle_finger::skin-tone-4": "🖕🏽", + "reversed_hand_with_middle_finger_extended::skin-tone-4": "🖕🏽", + "middle_finger::skin-tone-5": "🖕🏾", + "reversed_hand_with_middle_finger_extended::skin-tone-5": "🖕🏾", + "middle_finger::skin-tone-6": "🖕🏿", + "reversed_hand_with_middle_finger_extended::skin-tone-6": "🖕🏿", + "point_down": "👇", + "point_down::skin-tone-2": "👇🏻", + "point_down::skin-tone-3": "👇🏼", + "point_down::skin-tone-4": "👇🏽", + "point_down::skin-tone-5": "👇🏾", + "point_down::skin-tone-6": "👇🏿", + "point_up": "☝️", + "point_up::skin-tone-2": "☝🏻", + "point_up::skin-tone-3": "☝🏼", + "point_up::skin-tone-4": "☝🏽", + "point_up::skin-tone-5": "☝🏾", + "point_up::skin-tone-6": "☝🏿", + "index_pointing_at_the_viewer": "🫵", + "index_pointing_at_the_viewer::skin-tone-2": "🫵🏻", + "index_pointing_at_the_viewer::skin-tone-3": "🫵🏼", + "index_pointing_at_the_viewer::skin-tone-4": "🫵🏽", + "index_pointing_at_the_viewer::skin-tone-5": "🫵🏾", + "index_pointing_at_the_viewer::skin-tone-6": "🫵🏿", + "+1": "👍", + "thumbsup": "👍", + "+1::skin-tone-2": "👍🏻", + "thumbsup::skin-tone-2": "👍🏻", + "+1::skin-tone-3": "👍🏼", + "thumbsup::skin-tone-3": "👍🏼", + "+1::skin-tone-4": "👍🏽", + "thumbsup::skin-tone-4": "👍🏽", + "+1::skin-tone-5": "👍🏾", + "thumbsup::skin-tone-5": "👍🏾", + "+1::skin-tone-6": "👍🏿", + "thumbsup::skin-tone-6": "👍🏿", + "-1": "👎", + "thumbsdown": "👎", + "-1::skin-tone-2": "👎🏻", + "thumbsdown::skin-tone-2": "👎🏻", + "-1::skin-tone-3": "👎🏼", + "thumbsdown::skin-tone-3": "👎🏼", + "-1::skin-tone-4": "👎🏽", + "thumbsdown::skin-tone-4": "👎🏽", + "-1::skin-tone-5": "👎🏾", + "thumbsdown::skin-tone-5": "👎🏾", + "-1::skin-tone-6": "👎🏿", + "thumbsdown::skin-tone-6": "👎🏿", + "fist": "✊", + "fist::skin-tone-2": "✊🏻", + "fist::skin-tone-3": "✊🏼", + "fist::skin-tone-4": "✊🏽", + "fist::skin-tone-5": "✊🏾", + "fist::skin-tone-6": "✊🏿", + "facepunch": "👊", + "punch": "👊", + "facepunch::skin-tone-2": "👊🏻", + "punch::skin-tone-2": "👊🏻", + "facepunch::skin-tone-3": "👊🏼", + "punch::skin-tone-3": "👊🏼", + "facepunch::skin-tone-4": "👊🏽", + "punch::skin-tone-4": "👊🏽", + "facepunch::skin-tone-5": "👊🏾", + "punch::skin-tone-5": "👊🏾", + "facepunch::skin-tone-6": "👊🏿", + "punch::skin-tone-6": "👊🏿", + "left-facing_fist": "🤛", + "left-facing_fist::skin-tone-2": "🤛🏻", + "left-facing_fist::skin-tone-3": "🤛🏼", + "left-facing_fist::skin-tone-4": "🤛🏽", + "left-facing_fist::skin-tone-5": "🤛🏾", + "left-facing_fist::skin-tone-6": "🤛🏿", + "right-facing_fist": "🤜", + "right-facing_fist::skin-tone-2": "🤜🏻", + "right-facing_fist::skin-tone-3": "🤜🏼", + "right-facing_fist::skin-tone-4": "🤜🏽", + "right-facing_fist::skin-tone-5": "🤜🏾", + "right-facing_fist::skin-tone-6": "🤜🏿", + "clap": "👏", + "clap::skin-tone-2": "👏🏻", + "clap::skin-tone-3": "👏🏼", + "clap::skin-tone-4": "👏🏽", + "clap::skin-tone-5": "👏🏾", + "clap::skin-tone-6": "👏🏿", + "raised_hands": "🙌", + "raised_hands::skin-tone-2": "🙌🏻", + "raised_hands::skin-tone-3": "🙌🏼", + "raised_hands::skin-tone-4": "🙌🏽", + "raised_hands::skin-tone-5": "🙌🏾", + "raised_hands::skin-tone-6": "🙌🏿", + "heart_hands": "🫶", + "heart_hands::skin-tone-2": "🫶🏻", + "heart_hands::skin-tone-3": "🫶🏼", + "heart_hands::skin-tone-4": "🫶🏽", + "heart_hands::skin-tone-5": "🫶🏾", + "heart_hands::skin-tone-6": "🫶🏿", + "open_hands": "👐", + "open_hands::skin-tone-2": "👐🏻", + "open_hands::skin-tone-3": "👐🏼", + "open_hands::skin-tone-4": "👐🏽", + "open_hands::skin-tone-5": "👐🏾", + "open_hands::skin-tone-6": "👐🏿", + "palms_up_together": "🤲", + "palms_up_together::skin-tone-2": "🤲🏻", + "palms_up_together::skin-tone-3": "🤲🏼", + "palms_up_together::skin-tone-4": "🤲🏽", + "palms_up_together::skin-tone-5": "🤲🏾", + "palms_up_together::skin-tone-6": "🤲🏿", + "handshake": "🤝", + "handshake::skin-tone-2": "🤝🏻", + "handshake::skin-tone-3": "🤝🏼", + "handshake::skin-tone-4": "🤝🏽", + "handshake::skin-tone-5": "🤝🏾", + "handshake::skin-tone-6": "🤝🏿", + "pray": "🙏", + "pray::skin-tone-2": "🙏🏻", + "pray::skin-tone-3": "🙏🏼", + "pray::skin-tone-4": "🙏🏽", + "pray::skin-tone-5": "🙏🏾", + "pray::skin-tone-6": "🙏🏿", + "writing_hand": "✍️", + "writing_hand::skin-tone-2": "✍🏻", + "writing_hand::skin-tone-3": "✍🏼", + "writing_hand::skin-tone-4": "✍🏽", + "writing_hand::skin-tone-5": "✍🏾", + "writing_hand::skin-tone-6": "✍🏿", + "nail_care": "💅", + "nail_care::skin-tone-2": "💅🏻", + "nail_care::skin-tone-3": "💅🏼", + "nail_care::skin-tone-4": "💅🏽", + "nail_care::skin-tone-5": "💅🏾", + "nail_care::skin-tone-6": "💅🏿", + "selfie": "🤳", + "selfie::skin-tone-2": "🤳🏻", + "selfie::skin-tone-3": "🤳🏼", + "selfie::skin-tone-4": "🤳🏽", + "selfie::skin-tone-5": "🤳🏾", + "selfie::skin-tone-6": "🤳🏿", + "muscle": "💪", + "muscle::skin-tone-2": "💪🏻", + "muscle::skin-tone-3": "💪🏼", + "muscle::skin-tone-4": "💪🏽", + "muscle::skin-tone-5": "💪🏾", + "muscle::skin-tone-6": "💪🏿", + "mechanical_arm": "🦾", + "mechanical_leg": "🦿", + "leg": "🦵", + "leg::skin-tone-2": "🦵🏻", + "leg::skin-tone-3": "🦵🏼", + "leg::skin-tone-4": "🦵🏽", + "leg::skin-tone-5": "🦵🏾", + "leg::skin-tone-6": "🦵🏿", + "foot": "🦶", + "foot::skin-tone-2": "🦶🏻", + "foot::skin-tone-3": "🦶🏼", + "foot::skin-tone-4": "🦶🏽", + "foot::skin-tone-5": "🦶🏾", + "foot::skin-tone-6": "🦶🏿", + "ear": "👂", + "ear::skin-tone-2": "👂🏻", + "ear::skin-tone-3": "👂🏼", + "ear::skin-tone-4": "👂🏽", + "ear::skin-tone-5": "👂🏾", + "ear::skin-tone-6": "👂🏿", + "ear_with_hearing_aid": "🦻", + "ear_with_hearing_aid::skin-tone-2": "🦻🏻", + "ear_with_hearing_aid::skin-tone-3": "🦻🏼", + "ear_with_hearing_aid::skin-tone-4": "🦻🏽", + "ear_with_hearing_aid::skin-tone-5": "🦻🏾", + "ear_with_hearing_aid::skin-tone-6": "🦻🏿", + "nose": "👃", + "nose::skin-tone-2": "👃🏻", + "nose::skin-tone-3": "👃🏼", + "nose::skin-tone-4": "👃🏽", + "nose::skin-tone-5": "👃🏾", + "nose::skin-tone-6": "👃🏿", + "brain": "🧠", + "anatomical_heart": "🫀", + "lungs": "🫁", + "tooth": "🦷", + "bone": "🦴", + "eyes": "👀", + "dag-eyes": "👀", + "eye": "👁️", + "tongue": "👅", + "lips": "👄", + "biting_lip": "🫦", + "baby": "👶", + "baby::skin-tone-2": "👶🏻", + "baby::skin-tone-3": "👶🏼", + "baby::skin-tone-4": "👶🏽", + "baby::skin-tone-5": "👶🏾", + "baby::skin-tone-6": "👶🏿", + "child": "🧒", + "child::skin-tone-2": "🧒🏻", + "child::skin-tone-3": "🧒🏼", + "child::skin-tone-4": "🧒🏽", + "child::skin-tone-5": "🧒🏾", + "child::skin-tone-6": "🧒🏿", + "boy": "👦", + "boy::skin-tone-2": "👦🏻", + "boy::skin-tone-3": "👦🏼", + "boy::skin-tone-4": "👦🏽", + "boy::skin-tone-5": "👦🏾", + "boy::skin-tone-6": "👦🏿", + "girl": "👧", + "girl::skin-tone-2": "👧🏻", + "girl::skin-tone-3": "👧🏼", + "girl::skin-tone-4": "👧🏽", + "girl::skin-tone-5": "👧🏾", + "girl::skin-tone-6": "👧🏿", + "adult": "🧑", + "adult::skin-tone-2": "🧑🏻", + "adult::skin-tone-3": "🧑🏼", + "adult::skin-tone-4": "🧑🏽", + "adult::skin-tone-5": "🧑🏾", + "adult::skin-tone-6": "🧑🏿", + "person_with_blond_hair": "👱", + "person_with_blond_hair::skin-tone-2": "👱🏻", + "person_with_blond_hair::skin-tone-3": "👱🏼", + "person_with_blond_hair::skin-tone-4": "👱🏽", + "person_with_blond_hair::skin-tone-5": "👱🏾", + "person_with_blond_hair::skin-tone-6": "👱🏿", + "man": "👨", + "man::skin-tone-2": "👨🏻", + "man::skin-tone-3": "👨🏼", + "man::skin-tone-4": "👨🏽", + "man::skin-tone-5": "👨🏾", + "man::skin-tone-6": "👨🏿", + "bearded_person": "🧔", + "bearded_person::skin-tone-2": "🧔🏻", + "bearded_person::skin-tone-3": "🧔🏼", + "bearded_person::skin-tone-4": "🧔🏽", + "bearded_person::skin-tone-5": "🧔🏾", + "bearded_person::skin-tone-6": "🧔🏿", + "man_with_beard": "🧔♂️", + "man_with_beard::skin-tone-2": "🧔🏻♂️", + "man_with_beard::skin-tone-3": "🧔🏼♂️", + "man_with_beard::skin-tone-4": "🧔🏽♂️", + "man_with_beard::skin-tone-5": "🧔🏾♂️", + "man_with_beard::skin-tone-6": "🧔🏿♂️", + "woman_with_beard": "🧔♀️", + "woman_with_beard::skin-tone-2": "🧔🏻♀️", + "woman_with_beard::skin-tone-3": "🧔🏼♀️", + "woman_with_beard::skin-tone-4": "🧔🏽♀️", + "woman_with_beard::skin-tone-5": "🧔🏾♀️", + "woman_with_beard::skin-tone-6": "🧔🏿♀️", + "red_haired_man": "👨🦰", + "red_haired_man::skin-tone-2": "👨🏻🦰", + "red_haired_man::skin-tone-3": "👨🏼🦰", + "red_haired_man::skin-tone-4": "👨🏽🦰", + "red_haired_man::skin-tone-5": "👨🏾🦰", + "red_haired_man::skin-tone-6": "👨🏿🦰", + "curly_haired_man": "👨🦱", + "curly_haired_man::skin-tone-2": "👨🏻🦱", + "curly_haired_man::skin-tone-3": "👨🏼🦱", + "curly_haired_man::skin-tone-4": "👨🏽🦱", + "curly_haired_man::skin-tone-5": "👨🏾🦱", + "curly_haired_man::skin-tone-6": "👨🏿🦱", + "white_haired_man": "👨🦳", + "white_haired_man::skin-tone-2": "👨🏻🦳", + "white_haired_man::skin-tone-3": "👨🏼🦳", + "white_haired_man::skin-tone-4": "👨🏽🦳", + "white_haired_man::skin-tone-5": "👨🏾🦳", + "white_haired_man::skin-tone-6": "👨🏿🦳", + "bald_man": "👨🦲", + "bald_man::skin-tone-2": "👨🏻🦲", + "bald_man::skin-tone-3": "👨🏼🦲", + "bald_man::skin-tone-4": "👨🏽🦲", + "bald_man::skin-tone-5": "👨🏾🦲", + "bald_man::skin-tone-6": "👨🏿🦲", + "woman": "👩", + "woman::skin-tone-2": "👩🏻", + "woman::skin-tone-3": "👩🏼", + "woman::skin-tone-4": "👩🏽", + "woman::skin-tone-5": "👩🏾", + "woman::skin-tone-6": "👩🏿", + "red_haired_woman": "👩🦰", + "red_haired_woman::skin-tone-2": "👩🏻🦰", + "red_haired_woman::skin-tone-3": "👩🏼🦰", + "red_haired_woman::skin-tone-4": "👩🏽🦰", + "red_haired_woman::skin-tone-5": "👩🏾🦰", + "red_haired_woman::skin-tone-6": "👩🏿🦰", + "red_haired_person": "🧑🦰", + "red_haired_person::skin-tone-2": "🧑🏻🦰", + "red_haired_person::skin-tone-3": "🧑🏼🦰", + "red_haired_person::skin-tone-4": "🧑🏽🦰", + "red_haired_person::skin-tone-5": "🧑🏾🦰", + "red_haired_person::skin-tone-6": "🧑🏿🦰", + "curly_haired_woman": "👩🦱", + "curly_haired_woman::skin-tone-2": "👩🏻🦱", + "curly_haired_woman::skin-tone-3": "👩🏼🦱", + "curly_haired_woman::skin-tone-4": "👩🏽🦱", + "curly_haired_woman::skin-tone-5": "👩🏾🦱", + "curly_haired_woman::skin-tone-6": "👩🏿🦱", + "curly_haired_person": "🧑🦱", + "curly_haired_person::skin-tone-2": "🧑🏻🦱", + "curly_haired_person::skin-tone-3": "🧑🏼🦱", + "curly_haired_person::skin-tone-4": "🧑🏽🦱", + "curly_haired_person::skin-tone-5": "🧑🏾🦱", + "curly_haired_person::skin-tone-6": "🧑🏿🦱", + "white_haired_woman": "👩🦳", + "white_haired_woman::skin-tone-2": "👩🏻🦳", + "white_haired_woman::skin-tone-3": "👩🏼🦳", + "white_haired_woman::skin-tone-4": "👩🏽🦳", + "white_haired_woman::skin-tone-5": "👩🏾🦳", + "white_haired_woman::skin-tone-6": "👩🏿🦳", + "white_haired_person": "🧑🦳", + "white_haired_person::skin-tone-2": "🧑🏻🦳", + "white_haired_person::skin-tone-3": "🧑🏼🦳", + "white_haired_person::skin-tone-4": "🧑🏽🦳", + "white_haired_person::skin-tone-5": "🧑🏾🦳", + "white_haired_person::skin-tone-6": "🧑🏿🦳", + "bald_woman": "👩🦲", + "bald_woman::skin-tone-2": "👩🏻🦲", + "bald_woman::skin-tone-3": "👩🏼🦲", + "bald_woman::skin-tone-4": "👩🏽🦲", + "bald_woman::skin-tone-5": "👩🏾🦲", + "bald_woman::skin-tone-6": "👩🏿🦲", + "bald_person": "🧑🦲", + "bald_person::skin-tone-2": "🧑🏻🦲", + "bald_person::skin-tone-3": "🧑🏼🦲", + "bald_person::skin-tone-4": "🧑🏽🦲", + "bald_person::skin-tone-5": "🧑🏾🦲", + "bald_person::skin-tone-6": "🧑🏿🦲", + "blond-haired-woman": "👱♀️", + "blond-haired-woman::skin-tone-2": "👱🏻♀️", + "blond-haired-woman::skin-tone-3": "👱🏼♀️", + "blond-haired-woman::skin-tone-4": "👱🏽♀️", + "blond-haired-woman::skin-tone-5": "👱🏾♀️", + "blond-haired-woman::skin-tone-6": "👱🏿♀️", + "blond-haired-man": "👱♂️", + "blond-haired-man::skin-tone-2": "👱🏻♂️", + "blond-haired-man::skin-tone-3": "👱🏼♂️", + "blond-haired-man::skin-tone-4": "👱🏽♂️", + "blond-haired-man::skin-tone-5": "👱🏾♂️", + "blond-haired-man::skin-tone-6": "👱🏿♂️", + "older_adult": "🧓", + "older_adult::skin-tone-2": "🧓🏻", + "older_adult::skin-tone-3": "🧓🏼", + "older_adult::skin-tone-4": "🧓🏽", + "older_adult::skin-tone-5": "🧓🏾", + "older_adult::skin-tone-6": "🧓🏿", + "older_man": "👴", + "older_man::skin-tone-2": "👴🏻", + "older_man::skin-tone-3": "👴🏼", + "older_man::skin-tone-4": "👴🏽", + "older_man::skin-tone-5": "👴🏾", + "older_man::skin-tone-6": "👴🏿", + "older_woman": "👵", + "older_woman::skin-tone-2": "👵🏻", + "older_woman::skin-tone-3": "👵🏼", + "older_woman::skin-tone-4": "👵🏽", + "older_woman::skin-tone-5": "👵🏾", + "older_woman::skin-tone-6": "👵🏿", + "person_frowning": "🙍", + "person_frowning::skin-tone-2": "🙍🏻", + "person_frowning::skin-tone-3": "🙍🏼", + "person_frowning::skin-tone-4": "🙍🏽", + "person_frowning::skin-tone-5": "🙍🏾", + "person_frowning::skin-tone-6": "🙍🏿", + "man-frowning": "🙍♂️", + "man-frowning::skin-tone-2": "🙍🏻♂️", + "man-frowning::skin-tone-3": "🙍🏼♂️", + "man-frowning::skin-tone-4": "🙍🏽♂️", + "man-frowning::skin-tone-5": "🙍🏾♂️", + "man-frowning::skin-tone-6": "🙍🏿♂️", + "woman-frowning": "🙍♀️", + "woman-frowning::skin-tone-2": "🙍🏻♀️", + "woman-frowning::skin-tone-3": "🙍🏼♀️", + "woman-frowning::skin-tone-4": "🙍🏽♀️", + "woman-frowning::skin-tone-5": "🙍🏾♀️", + "woman-frowning::skin-tone-6": "🙍🏿♀️", + "person_with_pouting_face": "🙎", + "person_with_pouting_face::skin-tone-2": "🙎🏻", + "person_with_pouting_face::skin-tone-3": "🙎🏼", + "person_with_pouting_face::skin-tone-4": "🙎🏽", + "person_with_pouting_face::skin-tone-5": "🙎🏾", + "person_with_pouting_face::skin-tone-6": "🙎🏿", + "man-pouting": "🙎♂️", + "man-pouting::skin-tone-2": "🙎🏻♂️", + "man-pouting::skin-tone-3": "🙎🏼♂️", + "man-pouting::skin-tone-4": "🙎🏽♂️", + "man-pouting::skin-tone-5": "🙎🏾♂️", + "man-pouting::skin-tone-6": "🙎🏿♂️", + "woman-pouting": "🙎♀️", + "woman-pouting::skin-tone-2": "🙎🏻♀️", + "woman-pouting::skin-tone-3": "🙎🏼♀️", + "woman-pouting::skin-tone-4": "🙎🏽♀️", + "woman-pouting::skin-tone-5": "🙎🏾♀️", + "woman-pouting::skin-tone-6": "🙎🏿♀️", + "no_good": "🙅", + "no_good::skin-tone-2": "🙅🏻", + "no_good::skin-tone-3": "🙅🏼", + "no_good::skin-tone-4": "🙅🏽", + "no_good::skin-tone-5": "🙅🏾", + "no_good::skin-tone-6": "🙅🏿", + "man-gesturing-no": "🙅♂️", + "man-gesturing-no::skin-tone-2": "🙅🏻♂️", + "man-gesturing-no::skin-tone-3": "🙅🏼♂️", + "man-gesturing-no::skin-tone-4": "🙅🏽♂️", + "man-gesturing-no::skin-tone-5": "🙅🏾♂️", + "man-gesturing-no::skin-tone-6": "🙅🏿♂️", + "woman-gesturing-no": "🙅♀️", + "woman-gesturing-no::skin-tone-2": "🙅🏻♀️", + "woman-gesturing-no::skin-tone-3": "🙅🏼♀️", + "woman-gesturing-no::skin-tone-4": "🙅🏽♀️", + "woman-gesturing-no::skin-tone-5": "🙅🏾♀️", + "woman-gesturing-no::skin-tone-6": "🙅🏿♀️", + "ok_woman": "🙆", + "ok_woman::skin-tone-2": "🙆🏻", + "ok_woman::skin-tone-3": "🙆🏼", + "ok_woman::skin-tone-4": "🙆🏽", + "ok_woman::skin-tone-5": "🙆🏾", + "ok_woman::skin-tone-6": "🙆🏿", + "man-gesturing-ok": "🙆♂️", + "man-gesturing-ok::skin-tone-2": "🙆🏻♂️", + "man-gesturing-ok::skin-tone-3": "🙆🏼♂️", + "man-gesturing-ok::skin-tone-4": "🙆🏽♂️", + "man-gesturing-ok::skin-tone-5": "🙆🏾♂️", + "man-gesturing-ok::skin-tone-6": "🙆🏿♂️", + "woman-gesturing-ok": "🙆♀️", + "woman-gesturing-ok::skin-tone-2": "🙆🏻♀️", + "woman-gesturing-ok::skin-tone-3": "🙆🏼♀️", + "woman-gesturing-ok::skin-tone-4": "🙆🏽♀️", + "woman-gesturing-ok::skin-tone-5": "🙆🏾♀️", + "woman-gesturing-ok::skin-tone-6": "🙆🏿♀️", + "information_desk_person": "💁", + "information_desk_person::skin-tone-2": "💁🏻", + "information_desk_person::skin-tone-3": "💁🏼", + "information_desk_person::skin-tone-4": "💁🏽", + "information_desk_person::skin-tone-5": "💁🏾", + "information_desk_person::skin-tone-6": "💁🏿", + "man-tipping-hand": "💁♂️", + "man-tipping-hand::skin-tone-2": "💁🏻♂️", + "man-tipping-hand::skin-tone-3": "💁🏼♂️", + "man-tipping-hand::skin-tone-4": "💁🏽♂️", + "man-tipping-hand::skin-tone-5": "💁🏾♂️", + "man-tipping-hand::skin-tone-6": "💁🏿♂️", + "woman-tipping-hand": "💁♀️", + "woman-tipping-hand::skin-tone-2": "💁🏻♀️", + "woman-tipping-hand::skin-tone-3": "💁🏼♀️", + "woman-tipping-hand::skin-tone-4": "💁🏽♀️", + "woman-tipping-hand::skin-tone-5": "💁🏾♀️", + "woman-tipping-hand::skin-tone-6": "💁🏿♀️", + "raising_hand": "🙋", + "raising_hand::skin-tone-2": "🙋🏻", + "raising_hand::skin-tone-3": "🙋🏼", + "raising_hand::skin-tone-4": "🙋🏽", + "raising_hand::skin-tone-5": "🙋🏾", + "raising_hand::skin-tone-6": "🙋🏿", + "man-raising-hand": "🙋♂️", + "man-raising-hand::skin-tone-2": "🙋🏻♂️", + "man-raising-hand::skin-tone-3": "🙋🏼♂️", + "man-raising-hand::skin-tone-4": "🙋🏽♂️", + "man-raising-hand::skin-tone-5": "🙋🏾♂️", + "man-raising-hand::skin-tone-6": "🙋🏿♂️", + "woman-raising-hand": "🙋♀️", + "woman-raising-hand::skin-tone-2": "🙋🏻♀️", + "woman-raising-hand::skin-tone-3": "🙋🏼♀️", + "woman-raising-hand::skin-tone-4": "🙋🏽♀️", + "woman-raising-hand::skin-tone-5": "🙋🏾♀️", + "woman-raising-hand::skin-tone-6": "🙋🏿♀️", + "deaf_person": "🧏", + "deaf_person::skin-tone-2": "🧏🏻", + "deaf_person::skin-tone-3": "🧏🏼", + "deaf_person::skin-tone-4": "🧏🏽", + "deaf_person::skin-tone-5": "🧏🏾", + "deaf_person::skin-tone-6": "🧏🏿", + "deaf_man": "🧏♂️", + "deaf_man::skin-tone-2": "🧏🏻♂️", + "deaf_man::skin-tone-3": "🧏🏼♂️", + "deaf_man::skin-tone-4": "🧏🏽♂️", + "deaf_man::skin-tone-5": "🧏🏾♂️", + "deaf_man::skin-tone-6": "🧏🏿♂️", + "deaf_woman": "🧏♀️", + "deaf_woman::skin-tone-2": "🧏🏻♀️", + "deaf_woman::skin-tone-3": "🧏🏼♀️", + "deaf_woman::skin-tone-4": "🧏🏽♀️", + "deaf_woman::skin-tone-5": "🧏🏾♀️", + "deaf_woman::skin-tone-6": "🧏🏿♀️", + "nod-nicholson": "🙇", + "daggy-celebrate": "🎉", + "bow": "🙇", + "bow::skin-tone-2": "🙇🏻", + "bow::skin-tone-3": "🙇🏼", + "bow::skin-tone-4": "🙇🏽", + "bow::skin-tone-5": "🙇🏾", + "bow::skin-tone-6": "🙇🏿", + "man-bowing": "🙇♂️", + "man-bowing::skin-tone-2": "🙇🏻♂️", + "man-bowing::skin-tone-3": "🙇🏼♂️", + "man-bowing::skin-tone-4": "🙇🏽♂️", + "man-bowing::skin-tone-5": "🙇🏾♂️", + "man-bowing::skin-tone-6": "🙇🏿♂️", + "woman-bowing": "🙇♀️", + "woman-bowing::skin-tone-2": "🙇🏻♀️", + "woman-bowing::skin-tone-3": "🙇🏼♀️", + "woman-bowing::skin-tone-4": "🙇🏽♀️", + "woman-bowing::skin-tone-5": "🙇🏾♀️", + "woman-bowing::skin-tone-6": "🙇🏿♀️", + "face_palm": "🤦", + "face_palm::skin-tone-2": "🤦🏻", + "face_palm::skin-tone-3": "🤦🏼", + "face_palm::skin-tone-4": "🤦🏽", + "face_palm::skin-tone-5": "🤦🏾", + "face_palm::skin-tone-6": "🤦🏿", + "man-facepalming": "🤦♂️", + "man-facepalming::skin-tone-2": "🤦🏻♂️", + "man-facepalming::skin-tone-3": "🤦🏼♂️", + "man-facepalming::skin-tone-4": "🤦🏽♂️", + "man-facepalming::skin-tone-5": "🤦🏾♂️", + "man-facepalming::skin-tone-6": "🤦🏿♂️", + "woman-facepalming": "🤦♀️", + "woman-facepalming::skin-tone-2": "🤦🏻♀️", + "woman-facepalming::skin-tone-3": "🤦🏼♀️", + "woman-facepalming::skin-tone-4": "🤦🏽♀️", + "woman-facepalming::skin-tone-5": "🤦🏾♀️", + "woman-facepalming::skin-tone-6": "🤦🏿♀️", + "shrug": "🤷", + "shrug::skin-tone-2": "🤷🏻", + "shrug::skin-tone-3": "🤷🏼", + "shrug::skin-tone-4": "🤷🏽", + "shrug::skin-tone-5": "🤷🏾", + "shrug::skin-tone-6": "🤷🏿", + "man-shrugging": "🤷♂️", + "man-shrugging::skin-tone-2": "🤷🏻♂️", + "man-shrugging::skin-tone-3": "🤷🏼♂️", + "man-shrugging::skin-tone-4": "🤷🏽♂️", + "man-shrugging::skin-tone-5": "🤷🏾♂️", + "man-shrugging::skin-tone-6": "🤷🏿♂️", + "woman-shrugging": "🤷♀️", + "woman-shrugging::skin-tone-2": "🤷🏻♀️", + "woman-shrugging::skin-tone-3": "🤷🏼♀️", + "woman-shrugging::skin-tone-4": "🤷🏽♀️", + "woman-shrugging::skin-tone-5": "🤷🏾♀️", + "woman-shrugging::skin-tone-6": "🤷🏿♀️", + "health_worker": "🧑⚕️", + "health_worker::skin-tone-2": "🧑🏻⚕️", + "health_worker::skin-tone-3": "🧑🏼⚕️", + "health_worker::skin-tone-4": "🧑🏽⚕️", + "health_worker::skin-tone-5": "🧑🏾⚕️", + "health_worker::skin-tone-6": "🧑🏿⚕️", + "male-doctor": "👨⚕️", + "male-doctor::skin-tone-2": "👨🏻⚕️", + "male-doctor::skin-tone-3": "👨🏼⚕️", + "male-doctor::skin-tone-4": "👨🏽⚕️", + "male-doctor::skin-tone-5": "👨🏾⚕️", + "male-doctor::skin-tone-6": "👨🏿⚕️", + "female-doctor": "👩⚕️", + "female-doctor::skin-tone-2": "👩🏻⚕️", + "female-doctor::skin-tone-3": "👩🏼⚕️", + "female-doctor::skin-tone-4": "👩🏽⚕️", + "female-doctor::skin-tone-5": "👩🏾⚕️", + "female-doctor::skin-tone-6": "👩🏿⚕️", + "student": "🧑🎓", + "student::skin-tone-2": "🧑🏻🎓", + "student::skin-tone-3": "🧑🏼🎓", + "student::skin-tone-4": "🧑🏽🎓", + "student::skin-tone-5": "🧑🏾🎓", + "student::skin-tone-6": "🧑🏿🎓", + "male-student": "👨🎓", + "male-student::skin-tone-2": "👨🏻🎓", + "male-student::skin-tone-3": "👨🏼🎓", + "male-student::skin-tone-4": "👨🏽🎓", + "male-student::skin-tone-5": "👨🏾🎓", + "male-student::skin-tone-6": "👨🏿🎓", + "female-student": "👩🎓", + "female-student::skin-tone-2": "👩🏻🎓", + "female-student::skin-tone-3": "👩🏼🎓", + "female-student::skin-tone-4": "👩🏽🎓", + "female-student::skin-tone-5": "👩🏾🎓", + "female-student::skin-tone-6": "👩🏿🎓", + "teacher": "🧑🏫", + "teacher::skin-tone-2": "🧑🏻🏫", + "teacher::skin-tone-3": "🧑🏼🏫", + "teacher::skin-tone-4": "🧑🏽🏫", + "teacher::skin-tone-5": "🧑🏾🏫", + "teacher::skin-tone-6": "🧑🏿🏫", + "male-teacher": "👨🏫", + "male-teacher::skin-tone-2": "👨🏻🏫", + "male-teacher::skin-tone-3": "👨🏼🏫", + "male-teacher::skin-tone-4": "👨🏽🏫", + "male-teacher::skin-tone-5": "👨🏾🏫", + "male-teacher::skin-tone-6": "👨🏿🏫", + "female-teacher": "👩🏫", + "female-teacher::skin-tone-2": "👩🏻🏫", + "female-teacher::skin-tone-3": "👩🏼🏫", + "female-teacher::skin-tone-4": "👩🏽🏫", + "female-teacher::skin-tone-5": "👩🏾🏫", + "female-teacher::skin-tone-6": "👩🏿🏫", + "judge": "🧑⚖️", + "judge::skin-tone-2": "🧑🏻⚖️", + "judge::skin-tone-3": "🧑🏼⚖️", + "judge::skin-tone-4": "🧑🏽⚖️", + "judge::skin-tone-5": "🧑🏾⚖️", + "judge::skin-tone-6": "🧑🏿⚖️", + "male-judge": "👨⚖️", + "male-judge::skin-tone-2": "👨🏻⚖️", + "male-judge::skin-tone-3": "👨🏼⚖️", + "male-judge::skin-tone-4": "👨🏽⚖️", + "male-judge::skin-tone-5": "👨🏾⚖️", + "male-judge::skin-tone-6": "👨🏿⚖️", + "female-judge": "👩⚖️", + "female-judge::skin-tone-2": "👩🏻⚖️", + "female-judge::skin-tone-3": "👩🏼⚖️", + "female-judge::skin-tone-4": "👩🏽⚖️", + "female-judge::skin-tone-5": "👩🏾⚖️", + "female-judge::skin-tone-6": "👩🏿⚖️", + "farmer": "🧑🌾", + "farmer::skin-tone-2": "🧑🏻🌾", + "farmer::skin-tone-3": "🧑🏼🌾", + "farmer::skin-tone-4": "🧑🏽🌾", + "farmer::skin-tone-5": "🧑🏾🌾", + "farmer::skin-tone-6": "🧑🏿🌾", + "male-farmer": "👨🌾", + "male-farmer::skin-tone-2": "👨🏻🌾", + "male-farmer::skin-tone-3": "👨🏼🌾", + "male-farmer::skin-tone-4": "👨🏽🌾", + "male-farmer::skin-tone-5": "👨🏾🌾", + "male-farmer::skin-tone-6": "👨🏿🌾", + "female-farmer": "👩🌾", + "female-farmer::skin-tone-2": "👩🏻🌾", + "female-farmer::skin-tone-3": "👩🏼🌾", + "female-farmer::skin-tone-4": "👩🏽🌾", + "female-farmer::skin-tone-5": "👩🏾🌾", + "female-farmer::skin-tone-6": "👩🏿🌾", + "cook": "🧑🍳", + "cook::skin-tone-2": "🧑🏻🍳", + "cook::skin-tone-3": "🧑🏼🍳", + "cook::skin-tone-4": "🧑🏽🍳", + "cook::skin-tone-5": "🧑🏾🍳", + "cook::skin-tone-6": "🧑🏿🍳", + "male-cook": "👨🍳", + "male-cook::skin-tone-2": "👨🏻🍳", + "male-cook::skin-tone-3": "👨🏼🍳", + "male-cook::skin-tone-4": "👨🏽🍳", + "male-cook::skin-tone-5": "👨🏾🍳", + "male-cook::skin-tone-6": "👨🏿🍳", + "female-cook": "👩🍳", + "female-cook::skin-tone-2": "👩🏻🍳", + "female-cook::skin-tone-3": "👩🏼🍳", + "female-cook::skin-tone-4": "👩🏽🍳", + "female-cook::skin-tone-5": "👩🏾🍳", + "female-cook::skin-tone-6": "👩🏿🍳", + "mechanic": "🧑🔧", + "mechanic::skin-tone-2": "🧑🏻🔧", + "mechanic::skin-tone-3": "🧑🏼🔧", + "mechanic::skin-tone-4": "🧑🏽🔧", + "mechanic::skin-tone-5": "🧑🏾🔧", + "mechanic::skin-tone-6": "🧑🏿🔧", + "male-mechanic": "👨🔧", + "male-mechanic::skin-tone-2": "👨🏻🔧", + "male-mechanic::skin-tone-3": "👨🏼🔧", + "male-mechanic::skin-tone-4": "👨🏽🔧", + "male-mechanic::skin-tone-5": "👨🏾🔧", + "male-mechanic::skin-tone-6": "👨🏿🔧", + "female-mechanic": "👩🔧", + "female-mechanic::skin-tone-2": "👩🏻🔧", + "female-mechanic::skin-tone-3": "👩🏼🔧", + "female-mechanic::skin-tone-4": "👩🏽🔧", + "female-mechanic::skin-tone-5": "👩🏾🔧", + "female-mechanic::skin-tone-6": "👩🏿🔧", + "factory_worker": "🧑🏭", + "factory_worker::skin-tone-2": "🧑🏻🏭", + "factory_worker::skin-tone-3": "🧑🏼🏭", + "factory_worker::skin-tone-4": "🧑🏽🏭", + "factory_worker::skin-tone-5": "🧑🏾🏭", + "factory_worker::skin-tone-6": "🧑🏿🏭", + "male-factory-worker": "👨🏭", + "male-factory-worker::skin-tone-2": "👨🏻🏭", + "male-factory-worker::skin-tone-3": "👨🏼🏭", + "male-factory-worker::skin-tone-4": "👨🏽🏭", + "male-factory-worker::skin-tone-5": "👨🏾🏭", + "male-factory-worker::skin-tone-6": "👨🏿🏭", + "female-factory-worker": "👩🏭", + "female-factory-worker::skin-tone-2": "👩🏻🏭", + "female-factory-worker::skin-tone-3": "👩🏼🏭", + "female-factory-worker::skin-tone-4": "👩🏽🏭", + "female-factory-worker::skin-tone-5": "👩🏾🏭", + "female-factory-worker::skin-tone-6": "👩🏿🏭", + "office_worker": "🧑💼", + "office_worker::skin-tone-2": "🧑🏻💼", + "office_worker::skin-tone-3": "🧑🏼💼", + "office_worker::skin-tone-4": "🧑🏽💼", + "office_worker::skin-tone-5": "🧑🏾💼", + "office_worker::skin-tone-6": "🧑🏿💼", + "male-office-worker": "👨💼", + "male-office-worker::skin-tone-2": "👨🏻💼", + "male-office-worker::skin-tone-3": "👨🏼💼", + "male-office-worker::skin-tone-4": "👨🏽💼", + "male-office-worker::skin-tone-5": "👨🏾💼", + "male-office-worker::skin-tone-6": "👨🏿💼", + "female-office-worker": "👩💼", + "female-office-worker::skin-tone-2": "👩🏻💼", + "female-office-worker::skin-tone-3": "👩🏼💼", + "female-office-worker::skin-tone-4": "👩🏽💼", + "female-office-worker::skin-tone-5": "👩🏾💼", + "female-office-worker::skin-tone-6": "👩🏿💼", + "scientist": "🧑🔬", + "scientist::skin-tone-2": "🧑🏻🔬", + "scientist::skin-tone-3": "🧑🏼🔬", + "scientist::skin-tone-4": "🧑🏽🔬", + "scientist::skin-tone-5": "🧑🏾🔬", + "scientist::skin-tone-6": "🧑🏿🔬", + "male-scientist": "👨🔬", + "male-scientist::skin-tone-2": "👨🏻🔬", + "male-scientist::skin-tone-3": "👨🏼🔬", + "male-scientist::skin-tone-4": "👨🏽🔬", + "male-scientist::skin-tone-5": "👨🏾🔬", + "male-scientist::skin-tone-6": "👨🏿🔬", + "female-scientist": "👩🔬", + "female-scientist::skin-tone-2": "👩🏻🔬", + "female-scientist::skin-tone-3": "👩🏼🔬", + "female-scientist::skin-tone-4": "👩🏽🔬", + "female-scientist::skin-tone-5": "👩🏾🔬", + "female-scientist::skin-tone-6": "👩🏿🔬", + "technologist": "🧑💻", + "technologist::skin-tone-2": "🧑🏻💻", + "technologist::skin-tone-3": "🧑🏼💻", + "technologist::skin-tone-4": "🧑🏽💻", + "technologist::skin-tone-5": "🧑🏾💻", + "technologist::skin-tone-6": "🧑🏿💻", + "male-technologist": "👨💻", + "male-technologist::skin-tone-2": "👨🏻💻", + "male-technologist::skin-tone-3": "👨🏼💻", + "male-technologist::skin-tone-4": "👨🏽💻", + "male-technologist::skin-tone-5": "👨🏾💻", + "male-technologist::skin-tone-6": "👨🏿💻", + "female-technologist": "👩💻", + "female-technologist::skin-tone-2": "👩🏻💻", + "female-technologist::skin-tone-3": "👩🏼💻", + "female-technologist::skin-tone-4": "👩🏽💻", + "female-technologist::skin-tone-5": "👩🏾💻", + "female-technologist::skin-tone-6": "👩🏿💻", + "singer": "🧑🎤", + "singer::skin-tone-2": "🧑🏻🎤", + "singer::skin-tone-3": "🧑🏼🎤", + "singer::skin-tone-4": "🧑🏽🎤", + "singer::skin-tone-5": "🧑🏾🎤", + "singer::skin-tone-6": "🧑🏿🎤", + "male-singer": "👨🎤", + "male-singer::skin-tone-2": "👨🏻🎤", + "male-singer::skin-tone-3": "👨🏼🎤", + "male-singer::skin-tone-4": "👨🏽🎤", + "male-singer::skin-tone-5": "👨🏾🎤", + "male-singer::skin-tone-6": "👨🏿🎤", + "female-singer": "👩🎤", + "female-singer::skin-tone-2": "👩🏻🎤", + "female-singer::skin-tone-3": "👩🏼🎤", + "female-singer::skin-tone-4": "👩🏽🎤", + "female-singer::skin-tone-5": "👩🏾🎤", + "female-singer::skin-tone-6": "👩🏿🎤", + "artist": "🧑🎨", + "artist::skin-tone-2": "🧑🏻🎨", + "artist::skin-tone-3": "🧑🏼🎨", + "artist::skin-tone-4": "🧑🏽🎨", + "artist::skin-tone-5": "🧑🏾🎨", + "artist::skin-tone-6": "🧑🏿🎨", + "male-artist": "👨🎨", + "male-artist::skin-tone-2": "👨🏻🎨", + "male-artist::skin-tone-3": "👨🏼🎨", + "male-artist::skin-tone-4": "👨🏽🎨", + "male-artist::skin-tone-5": "👨🏾🎨", + "male-artist::skin-tone-6": "👨🏿🎨", + "female-artist": "👩🎨", + "female-artist::skin-tone-2": "👩🏻🎨", + "female-artist::skin-tone-3": "👩🏼🎨", + "female-artist::skin-tone-4": "👩🏽🎨", + "female-artist::skin-tone-5": "👩🏾🎨", + "female-artist::skin-tone-6": "👩🏿🎨", + "pilot": "🧑✈️", + "pilot::skin-tone-2": "🧑🏻✈️", + "pilot::skin-tone-3": "🧑🏼✈️", + "pilot::skin-tone-4": "🧑🏽✈️", + "pilot::skin-tone-5": "🧑🏾✈️", + "pilot::skin-tone-6": "🧑🏿✈️", + "male-pilot": "👨✈️", + "male-pilot::skin-tone-2": "👨🏻✈️", + "male-pilot::skin-tone-3": "👨🏼✈️", + "male-pilot::skin-tone-4": "👨🏽✈️", + "male-pilot::skin-tone-5": "👨🏾✈️", + "male-pilot::skin-tone-6": "👨🏿✈️", + "female-pilot": "👩✈️", + "female-pilot::skin-tone-2": "👩🏻✈️", + "female-pilot::skin-tone-3": "👩🏼✈️", + "female-pilot::skin-tone-4": "👩🏽✈️", + "female-pilot::skin-tone-5": "👩🏾✈️", + "female-pilot::skin-tone-6": "👩🏿✈️", + "astronaut": "🧑🚀", + "astronaut::skin-tone-2": "🧑🏻🚀", + "astronaut::skin-tone-3": "🧑🏼🚀", + "astronaut::skin-tone-4": "🧑🏽🚀", + "astronaut::skin-tone-5": "🧑🏾🚀", + "astronaut::skin-tone-6": "🧑🏿🚀", + "male-astronaut": "👨🚀", + "male-astronaut::skin-tone-2": "👨🏻🚀", + "male-astronaut::skin-tone-3": "👨🏼🚀", + "male-astronaut::skin-tone-4": "👨🏽🚀", + "male-astronaut::skin-tone-5": "👨🏾🚀", + "male-astronaut::skin-tone-6": "👨🏿🚀", + "female-astronaut": "👩🚀", + "female-astronaut::skin-tone-2": "👩🏻🚀", + "female-astronaut::skin-tone-3": "👩🏼🚀", + "female-astronaut::skin-tone-4": "👩🏽🚀", + "female-astronaut::skin-tone-5": "👩🏾🚀", + "female-astronaut::skin-tone-6": "👩🏿🚀", + "firefighter": "🧑🚒", + "firefighter::skin-tone-2": "🧑🏻🚒", + "firefighter::skin-tone-3": "🧑🏼🚒", + "firefighter::skin-tone-4": "🧑🏽🚒", + "firefighter::skin-tone-5": "🧑🏾🚒", + "firefighter::skin-tone-6": "🧑🏿🚒", + "male-firefighter": "👨🚒", + "male-firefighter::skin-tone-2": "👨🏻🚒", + "male-firefighter::skin-tone-3": "👨🏼🚒", + "male-firefighter::skin-tone-4": "👨🏽🚒", + "male-firefighter::skin-tone-5": "👨🏾🚒", + "male-firefighter::skin-tone-6": "👨🏿🚒", + "female-firefighter": "👩🚒", + "female-firefighter::skin-tone-2": "👩🏻🚒", + "female-firefighter::skin-tone-3": "👩🏼🚒", + "female-firefighter::skin-tone-4": "👩🏽🚒", + "female-firefighter::skin-tone-5": "👩🏾🚒", + "female-firefighter::skin-tone-6": "👩🏿🚒", + "cop": "👮", + "cop::skin-tone-2": "👮🏻", + "cop::skin-tone-3": "👮🏼", + "cop::skin-tone-4": "👮🏽", + "cop::skin-tone-5": "👮🏾", + "cop::skin-tone-6": "👮🏿", + "male-police-officer": "👮♂️", + "male-police-officer::skin-tone-2": "👮🏻♂️", + "male-police-officer::skin-tone-3": "👮🏼♂️", + "male-police-officer::skin-tone-4": "👮🏽♂️", + "male-police-officer::skin-tone-5": "👮🏾♂️", + "male-police-officer::skin-tone-6": "👮🏿♂️", + "female-police-officer": "👮♀️", + "female-police-officer::skin-tone-2": "👮🏻♀️", + "female-police-officer::skin-tone-3": "👮🏼♀️", + "female-police-officer::skin-tone-4": "👮🏽♀️", + "female-police-officer::skin-tone-5": "👮🏾♀️", + "female-police-officer::skin-tone-6": "👮🏿♀️", + "sleuth_or_spy": "🕵️", + "sleuth_or_spy::skin-tone-2": "🕵🏻", + "sleuth_or_spy::skin-tone-3": "🕵🏼", + "sleuth_or_spy::skin-tone-4": "🕵🏽", + "sleuth_or_spy::skin-tone-5": "🕵🏾", + "sleuth_or_spy::skin-tone-6": "🕵🏿", + "male-detective": "🕵️♂️", + "male-detective::skin-tone-2": "🕵🏻♂️", + "male-detective::skin-tone-3": "🕵🏼♂️", + "male-detective::skin-tone-4": "🕵🏽♂️", + "male-detective::skin-tone-5": "🕵🏾♂️", + "male-detective::skin-tone-6": "🕵🏿♂️", + "female-detective": "🕵️♀️", + "female-detective::skin-tone-2": "🕵🏻♀️", + "female-detective::skin-tone-3": "🕵🏼♀️", + "female-detective::skin-tone-4": "🕵🏽♀️", + "female-detective::skin-tone-5": "🕵🏾♀️", + "female-detective::skin-tone-6": "🕵🏿♀️", + "guardsman": "💂", + "guardsman::skin-tone-2": "💂🏻", + "guardsman::skin-tone-3": "💂🏼", + "guardsman::skin-tone-4": "💂🏽", + "guardsman::skin-tone-5": "💂🏾", + "guardsman::skin-tone-6": "💂🏿", + "male-guard": "💂♂️", + "male-guard::skin-tone-2": "💂🏻♂️", + "male-guard::skin-tone-3": "💂🏼♂️", + "male-guard::skin-tone-4": "💂🏽♂️", + "male-guard::skin-tone-5": "💂🏾♂️", + "male-guard::skin-tone-6": "💂🏿♂️", + "female-guard": "💂♀️", + "female-guard::skin-tone-2": "💂🏻♀️", + "female-guard::skin-tone-3": "💂🏼♀️", + "female-guard::skin-tone-4": "💂🏽♀️", + "female-guard::skin-tone-5": "💂🏾♀️", + "female-guard::skin-tone-6": "💂🏿♀️", + "ninja": "🥷", + "ninja::skin-tone-2": "🥷🏻", + "ninja::skin-tone-3": "🥷🏼", + "ninja::skin-tone-4": "🥷🏽", + "ninja::skin-tone-5": "🥷🏾", + "ninja::skin-tone-6": "🥷🏿", + "construction_worker": "👷", + "construction_worker::skin-tone-2": "👷🏻", + "construction_worker::skin-tone-3": "👷🏼", + "construction_worker::skin-tone-4": "👷🏽", + "construction_worker::skin-tone-5": "👷🏾", + "construction_worker::skin-tone-6": "👷🏿", + "male-construction-worker": "👷♂️", + "male-construction-worker::skin-tone-2": "👷🏻♂️", + "male-construction-worker::skin-tone-3": "👷🏼♂️", + "male-construction-worker::skin-tone-4": "👷🏽♂️", + "male-construction-worker::skin-tone-5": "👷🏾♂️", + "male-construction-worker::skin-tone-6": "👷🏿♂️", + "female-construction-worker": "👷♀️", + "female-construction-worker::skin-tone-2": "👷🏻♀️", + "female-construction-worker::skin-tone-3": "👷🏼♀️", + "female-construction-worker::skin-tone-4": "👷🏽♀️", + "female-construction-worker::skin-tone-5": "👷🏾♀️", + "female-construction-worker::skin-tone-6": "👷🏿♀️", + "person_with_crown": "🫅", + "person_with_crown::skin-tone-2": "🫅🏻", + "person_with_crown::skin-tone-3": "🫅🏼", + "person_with_crown::skin-tone-4": "🫅🏽", + "person_with_crown::skin-tone-5": "🫅🏾", + "person_with_crown::skin-tone-6": "🫅🏿", + "prince": "🤴", + "prince::skin-tone-2": "🤴🏻", + "prince::skin-tone-3": "🤴🏼", + "prince::skin-tone-4": "🤴🏽", + "prince::skin-tone-5": "🤴🏾", + "prince::skin-tone-6": "🤴🏿", + "princess": "👸", + "princess::skin-tone-2": "👸🏻", + "princess::skin-tone-3": "👸🏼", + "princess::skin-tone-4": "👸🏽", + "princess::skin-tone-5": "👸🏾", + "princess::skin-tone-6": "👸🏿", + "man_with_turban": "👳", + "man_with_turban::skin-tone-2": "👳🏻", + "man_with_turban::skin-tone-3": "👳🏼", + "man_with_turban::skin-tone-4": "👳🏽", + "man_with_turban::skin-tone-5": "👳🏾", + "man_with_turban::skin-tone-6": "👳🏿", + "man-wearing-turban": "👳♂️", + "man-wearing-turban::skin-tone-2": "👳🏻♂️", + "man-wearing-turban::skin-tone-3": "👳🏼♂️", + "man-wearing-turban::skin-tone-4": "👳🏽♂️", + "man-wearing-turban::skin-tone-5": "👳🏾♂️", + "man-wearing-turban::skin-tone-6": "👳🏿♂️", + "woman-wearing-turban": "👳♀️", + "woman-wearing-turban::skin-tone-2": "👳🏻♀️", + "woman-wearing-turban::skin-tone-3": "👳🏼♀️", + "woman-wearing-turban::skin-tone-4": "👳🏽♀️", + "woman-wearing-turban::skin-tone-5": "👳🏾♀️", + "woman-wearing-turban::skin-tone-6": "👳🏿♀️", + "man_with_gua_pi_mao": "👲", + "man_with_gua_pi_mao::skin-tone-2": "👲🏻", + "man_with_gua_pi_mao::skin-tone-3": "👲🏼", + "man_with_gua_pi_mao::skin-tone-4": "👲🏽", + "man_with_gua_pi_mao::skin-tone-5": "👲🏾", + "man_with_gua_pi_mao::skin-tone-6": "👲🏿", + "person_with_headscarf": "🧕", + "person_with_headscarf::skin-tone-2": "🧕🏻", + "person_with_headscarf::skin-tone-3": "🧕🏼", + "person_with_headscarf::skin-tone-4": "🧕🏽", + "person_with_headscarf::skin-tone-5": "🧕🏾", + "person_with_headscarf::skin-tone-6": "🧕🏿", + "person_in_tuxedo": "🤵", + "person_in_tuxedo::skin-tone-2": "🤵🏻", + "person_in_tuxedo::skin-tone-3": "🤵🏼", + "person_in_tuxedo::skin-tone-4": "🤵🏽", + "person_in_tuxedo::skin-tone-5": "🤵🏾", + "person_in_tuxedo::skin-tone-6": "🤵🏿", + "man_in_tuxedo": "🤵♂️", + "man_in_tuxedo::skin-tone-2": "🤵🏻♂️", + "man_in_tuxedo::skin-tone-3": "🤵🏼♂️", + "man_in_tuxedo::skin-tone-4": "🤵🏽♂️", + "man_in_tuxedo::skin-tone-5": "🤵🏾♂️", + "man_in_tuxedo::skin-tone-6": "🤵🏿♂️", + "woman_in_tuxedo": "🤵♀️", + "woman_in_tuxedo::skin-tone-2": "🤵🏻♀️", + "woman_in_tuxedo::skin-tone-3": "🤵🏼♀️", + "woman_in_tuxedo::skin-tone-4": "🤵🏽♀️", + "woman_in_tuxedo::skin-tone-5": "🤵🏾♀️", + "woman_in_tuxedo::skin-tone-6": "🤵🏿♀️", + "bride_with_veil": "👰", + "bride_with_veil::skin-tone-2": "👰🏻", + "bride_with_veil::skin-tone-3": "👰🏼", + "bride_with_veil::skin-tone-4": "👰🏽", + "bride_with_veil::skin-tone-5": "👰🏾", + "bride_with_veil::skin-tone-6": "👰🏿", + "man_with_veil": "👰♂️", + "man_with_veil::skin-tone-2": "👰🏻♂️", + "man_with_veil::skin-tone-3": "👰🏼♂️", + "man_with_veil::skin-tone-4": "👰🏽♂️", + "man_with_veil::skin-tone-5": "👰🏾♂️", + "man_with_veil::skin-tone-6": "👰🏿♂️", + "woman_with_veil": "👰♀️", + "woman_with_veil::skin-tone-2": "👰🏻♀️", + "woman_with_veil::skin-tone-3": "👰🏼♀️", + "woman_with_veil::skin-tone-4": "👰🏽♀️", + "woman_with_veil::skin-tone-5": "👰🏾♀️", + "woman_with_veil::skin-tone-6": "👰🏿♀️", + "pregnant_woman": "🤰", + "pregnant_woman::skin-tone-2": "🤰🏻", + "pregnant_woman::skin-tone-3": "🤰🏼", + "pregnant_woman::skin-tone-4": "🤰🏽", + "pregnant_woman::skin-tone-5": "🤰🏾", + "pregnant_woman::skin-tone-6": "🤰🏿", + "pregnant_man": "🫃", + "pregnant_man::skin-tone-2": "🫃🏻", + "pregnant_man::skin-tone-3": "🫃🏼", + "pregnant_man::skin-tone-4": "🫃🏽", + "pregnant_man::skin-tone-5": "🫃🏾", + "pregnant_man::skin-tone-6": "🫃🏿", + "pregnant_person": "🫄", + "pregnant_person::skin-tone-2": "🫄🏻", + "pregnant_person::skin-tone-3": "🫄🏼", + "pregnant_person::skin-tone-4": "🫄🏽", + "pregnant_person::skin-tone-5": "🫄🏾", + "pregnant_person::skin-tone-6": "🫄🏿", + "breast-feeding": "🤱", + "breast-feeding::skin-tone-2": "🤱🏻", + "breast-feeding::skin-tone-3": "🤱🏼", + "breast-feeding::skin-tone-4": "🤱🏽", + "breast-feeding::skin-tone-5": "🤱🏾", + "breast-feeding::skin-tone-6": "🤱🏿", + "woman_feeding_baby": "👩🍼", + "woman_feeding_baby::skin-tone-2": "👩🏻🍼", + "woman_feeding_baby::skin-tone-3": "👩🏼🍼", + "woman_feeding_baby::skin-tone-4": "👩🏽🍼", + "woman_feeding_baby::skin-tone-5": "👩🏾🍼", + "woman_feeding_baby::skin-tone-6": "👩🏿🍼", + "man_feeding_baby": "👨🍼", + "man_feeding_baby::skin-tone-2": "👨🏻🍼", + "man_feeding_baby::skin-tone-3": "👨🏼🍼", + "man_feeding_baby::skin-tone-4": "👨🏽🍼", + "man_feeding_baby::skin-tone-5": "👨🏾🍼", + "man_feeding_baby::skin-tone-6": "👨🏿🍼", + "person_feeding_baby": "🧑🍼", + "person_feeding_baby::skin-tone-2": "🧑🏻🍼", + "person_feeding_baby::skin-tone-3": "🧑🏼🍼", + "person_feeding_baby::skin-tone-4": "🧑🏽🍼", + "person_feeding_baby::skin-tone-5": "🧑🏾🍼", + "person_feeding_baby::skin-tone-6": "🧑🏿🍼", + "angel": "👼", + "angel::skin-tone-2": "👼🏻", + "angel::skin-tone-3": "👼🏼", + "angel::skin-tone-4": "👼🏽", + "angel::skin-tone-5": "👼🏾", + "angel::skin-tone-6": "👼🏿", + "dagsanta": "🎅", + "santa": "🎅", + "santa::skin-tone-2": "🎅🏻", + "santa::skin-tone-3": "🎅🏼", + "santa::skin-tone-4": "🎅🏽", + "santa::skin-tone-5": "🎅🏾", + "santa::skin-tone-6": "🎅🏿", + "mrs_claus": "🤶", + "mother_christmas": "🤶", + "mrs_claus::skin-tone-2": "🤶🏻", + "mother_christmas::skin-tone-2": "🤶🏻", + "mrs_claus::skin-tone-3": "🤶🏼", + "mother_christmas::skin-tone-3": "🤶🏼", + "mrs_claus::skin-tone-4": "🤶🏽", + "mother_christmas::skin-tone-4": "🤶🏽", + "mrs_claus::skin-tone-5": "🤶🏾", + "mother_christmas::skin-tone-5": "🤶🏾", + "mrs_claus::skin-tone-6": "🤶🏿", + "mother_christmas::skin-tone-6": "🤶🏿", + "mx_claus": "🧑🎄", + "mx_claus::skin-tone-2": "🧑🏻🎄", + "mx_claus::skin-tone-3": "🧑🏼🎄", + "mx_claus::skin-tone-4": "🧑🏽🎄", + "mx_claus::skin-tone-5": "🧑🏾🎄", + "mx_claus::skin-tone-6": "🧑🏿🎄", + "superhero": "🦸", + "superhero::skin-tone-2": "🦸🏻", + "superhero::skin-tone-3": "🦸🏼", + "superhero::skin-tone-4": "🦸🏽", + "superhero::skin-tone-5": "🦸🏾", + "superhero::skin-tone-6": "🦸🏿", + "male_superhero": "🦸♂️", + "male_superhero::skin-tone-2": "🦸🏻♂️", + "male_superhero::skin-tone-3": "🦸🏼♂️", + "male_superhero::skin-tone-4": "🦸🏽♂️", + "male_superhero::skin-tone-5": "🦸🏾♂️", + "male_superhero::skin-tone-6": "🦸🏿♂️", + "female_superhero": "🦸♀️", + "female_superhero::skin-tone-2": "🦸🏻♀️", + "female_superhero::skin-tone-3": "🦸🏼♀️", + "female_superhero::skin-tone-4": "🦸🏽♀️", + "female_superhero::skin-tone-5": "🦸🏾♀️", + "female_superhero::skin-tone-6": "🦸🏿♀️", + "supervillain": "🦹", + "supervillain::skin-tone-2": "🦹🏻", + "supervillain::skin-tone-3": "🦹🏼", + "supervillain::skin-tone-4": "🦹🏽", + "supervillain::skin-tone-5": "🦹🏾", + "supervillain::skin-tone-6": "🦹🏿", + "male_supervillain": "🦹♂️", + "male_supervillain::skin-tone-2": "🦹🏻♂️", + "male_supervillain::skin-tone-3": "🦹🏼♂️", + "male_supervillain::skin-tone-4": "🦹🏽♂️", + "male_supervillain::skin-tone-5": "🦹🏾♂️", + "male_supervillain::skin-tone-6": "🦹🏿♂️", + "female_supervillain": "🦹♀️", + "female_supervillain::skin-tone-2": "🦹🏻♀️", + "female_supervillain::skin-tone-3": "🦹🏼♀️", + "female_supervillain::skin-tone-4": "🦹🏽♀️", + "female_supervillain::skin-tone-5": "🦹🏾♀️", + "female_supervillain::skin-tone-6": "🦹🏿♀️", + "mage": "🧙", + "mage_ai": "🧙", + "mage::skin-tone-2": "🧙🏻", + "mage::skin-tone-3": "🧙🏼", + "mage::skin-tone-4": "🧙🏽", + "mage::skin-tone-5": "🧙🏾", + "mage::skin-tone-6": "🧙🏿", + "male_mage": "🧙♂️", + "male_mage::skin-tone-2": "🧙🏻♂️", + "male_mage::skin-tone-3": "🧙🏼♂️", + "male_mage::skin-tone-4": "🧙🏽♂️", + "male_mage::skin-tone-5": "🧙🏾♂️", + "male_mage::skin-tone-6": "🧙🏿♂️", + "female_mage": "🧙♀️", + "female_mage::skin-tone-2": "🧙🏻♀️", + "female_mage::skin-tone-3": "🧙🏼♀️", + "female_mage::skin-tone-4": "🧙🏽♀️", + "female_mage::skin-tone-5": "🧙🏾♀️", + "female_mage::skin-tone-6": "🧙🏿♀️", + "airflow": "A", + "fairy": "🧚", + "fairy::skin-tone-2": "🧚🏻", + "fairy::skin-tone-3": "🧚🏼", + "fairy::skin-tone-4": "🧚🏽", + "fairy::skin-tone-5": "🧚🏾", + "fairy::skin-tone-6": "🧚🏿", + "male_fairy": "🧚♂️", + "male_fairy::skin-tone-2": "🧚🏻♂️", + "male_fairy::skin-tone-3": "🧚🏼♂️", + "male_fairy::skin-tone-4": "🧚🏽♂️", + "male_fairy::skin-tone-5": "🧚🏾♂️", + "male_fairy::skin-tone-6": "🧚🏿♂️", + "female_fairy": "🧚♀️", + "female_fairy::skin-tone-2": "🧚🏻♀️", + "female_fairy::skin-tone-3": "🧚🏼♀️", + "female_fairy::skin-tone-4": "🧚🏽♀️", + "female_fairy::skin-tone-5": "🧚🏾♀️", + "female_fairy::skin-tone-6": "🧚🏿♀️", + "vampire": "🧛", + "vampire::skin-tone-2": "🧛🏻", + "vampire::skin-tone-3": "🧛🏼", + "vampire::skin-tone-4": "🧛🏽", + "vampire::skin-tone-5": "🧛🏾", + "vampire::skin-tone-6": "🧛🏿", + "male_vampire": "🧛♂️", + "male_vampire::skin-tone-2": "🧛🏻♂️", + "male_vampire::skin-tone-3": "🧛🏼♂️", + "male_vampire::skin-tone-4": "🧛🏽♂️", + "male_vampire::skin-tone-5": "🧛🏾♂️", + "male_vampire::skin-tone-6": "🧛🏿♂️", + "female_vampire": "🧛♀️", + "female_vampire::skin-tone-2": "🧛🏻♀️", + "female_vampire::skin-tone-3": "🧛🏼♀️", + "female_vampire::skin-tone-4": "🧛🏽♀️", + "female_vampire::skin-tone-5": "🧛🏾♀️", + "female_vampire::skin-tone-6": "🧛🏿♀️", + "merperson": "🧜", + "merperson::skin-tone-2": "🧜🏻", + "merperson::skin-tone-3": "🧜🏼", + "merperson::skin-tone-4": "🧜🏽", + "merperson::skin-tone-5": "🧜🏾", + "merperson::skin-tone-6": "🧜🏿", + "merman": "🧜♂️", + "merman::skin-tone-2": "🧜🏻♂️", + "merman::skin-tone-3": "🧜🏼♂️", + "merman::skin-tone-4": "🧜🏽♂️", + "merman::skin-tone-5": "🧜🏾♂️", + "merman::skin-tone-6": "🧜🏿♂️", + "mermaid": "🧜♀️", + "mermaid::skin-tone-2": "🧜🏻♀️", + "mermaid::skin-tone-3": "🧜🏼♀️", + "mermaid::skin-tone-4": "🧜🏽♀️", + "mermaid::skin-tone-5": "🧜🏾♀️", + "mermaid::skin-tone-6": "🧜🏿♀️", + "elf": "🧝", + "elf::skin-tone-2": "🧝🏻", + "elf::skin-tone-3": "🧝🏼", + "elf::skin-tone-4": "🧝🏽", + "elf::skin-tone-5": "🧝🏾", + "elf::skin-tone-6": "🧝🏿", + "male_elf": "🧝♂️", + "male_elf::skin-tone-2": "🧝🏻♂️", + "male_elf::skin-tone-3": "🧝🏼♂️", + "male_elf::skin-tone-4": "🧝🏽♂️", + "male_elf::skin-tone-5": "🧝🏾♂️", + "male_elf::skin-tone-6": "🧝🏿♂️", + "female_elf": "🧝♀️", + "female_elf::skin-tone-2": "🧝🏻♀️", + "female_elf::skin-tone-3": "🧝🏼♀️", + "female_elf::skin-tone-4": "🧝🏽♀️", + "female_elf::skin-tone-5": "🧝🏾♀️", + "female_elf::skin-tone-6": "🧝🏿♀️", + "genie": "🧞", + "male_genie": "🧞♂️", + "female_genie": "🧞♀️", + "zombie": "🧟", + "male_zombie": "🧟♂️", + "female_zombie": "🧟♀️", + "troll": "🧌", + "massage": "💆", + "massage::skin-tone-2": "💆🏻", + "massage::skin-tone-3": "💆🏼", + "massage::skin-tone-4": "💆🏽", + "massage::skin-tone-5": "💆🏾", + "massage::skin-tone-6": "💆🏿", + "man-getting-massage": "💆♂️", + "man-getting-massage::skin-tone-2": "💆🏻♂️", + "man-getting-massage::skin-tone-3": "💆🏼♂️", + "man-getting-massage::skin-tone-4": "💆🏽♂️", + "man-getting-massage::skin-tone-5": "💆🏾♂️", + "man-getting-massage::skin-tone-6": "💆🏿♂️", + "woman-getting-massage": "💆♀️", + "woman-getting-massage::skin-tone-2": "💆🏻♀️", + "woman-getting-massage::skin-tone-3": "💆🏼♀️", + "woman-getting-massage::skin-tone-4": "💆🏽♀️", + "woman-getting-massage::skin-tone-5": "💆🏾♀️", + "woman-getting-massage::skin-tone-6": "💆🏿♀️", + "haircut": "💇", + "haircut::skin-tone-2": "💇🏻", + "haircut::skin-tone-3": "💇🏼", + "haircut::skin-tone-4": "💇🏽", + "haircut::skin-tone-5": "💇🏾", + "haircut::skin-tone-6": "💇🏿", + "man-getting-haircut": "💇♂️", + "man-getting-haircut::skin-tone-2": "💇🏻♂️", + "man-getting-haircut::skin-tone-3": "💇🏼♂️", + "man-getting-haircut::skin-tone-4": "💇🏽♂️", + "man-getting-haircut::skin-tone-5": "💇🏾♂️", + "man-getting-haircut::skin-tone-6": "💇🏿♂️", + "woman-getting-haircut": "💇♀️", + "woman-getting-haircut::skin-tone-2": "💇🏻♀️", + "woman-getting-haircut::skin-tone-3": "💇🏼♀️", + "woman-getting-haircut::skin-tone-4": "💇🏽♀️", + "woman-getting-haircut::skin-tone-5": "💇🏾♀️", + "woman-getting-haircut::skin-tone-6": "💇🏿♀️", + "lfg": "🚶", + "walking": "🚶", + "walking::skin-tone-2": "🚶🏻", + "walking::skin-tone-3": "🚶🏼", + "walking::skin-tone-4": "🚶🏽", + "walking::skin-tone-5": "🚶🏾", + "walking::skin-tone-6": "🚶🏿", + "man-walking": "🚶♂️", + "man-walking::skin-tone-2": "🚶🏻♂️", + "man-walking::skin-tone-3": "🚶🏼♂️", + "man-walking::skin-tone-4": "🚶🏽♂️", + "man-walking::skin-tone-5": "🚶🏾♂️", + "man-walking::skin-tone-6": "🚶🏿♂️", + "woman-walking": "🚶♀️", + "woman-walking::skin-tone-2": "🚶🏻♀️", + "woman-walking::skin-tone-3": "🚶🏼♀️", + "woman-walking::skin-tone-4": "🚶🏽♀️", + "woman-walking::skin-tone-5": "🚶🏾♀️", + "woman-walking::skin-tone-6": "🚶🏿♀️", + "standing_person": "🧍", + "standing_person::skin-tone-2": "🧍🏻", + "standing_person::skin-tone-3": "🧍🏼", + "standing_person::skin-tone-4": "🧍🏽", + "standing_person::skin-tone-5": "🧍🏾", + "standing_person::skin-tone-6": "🧍🏿", + "man_standing": "🧍♂️", + "man_standing::skin-tone-2": "🧍🏻♂️", + "man_standing::skin-tone-3": "🧍🏼♂️", + "man_standing::skin-tone-4": "🧍🏽♂️", + "man_standing::skin-tone-5": "🧍🏾♂️", + "man_standing::skin-tone-6": "🧍🏿♂️", + "woman_standing": "🧍♀️", + "woman_standing::skin-tone-2": "🧍🏻♀️", + "woman_standing::skin-tone-3": "🧍🏼♀️", + "woman_standing::skin-tone-4": "🧍🏽♀️", + "woman_standing::skin-tone-5": "🧍🏾♀️", + "woman_standing::skin-tone-6": "🧍🏿♀️", + "kneeling_person": "🧎", + "kneeling_person::skin-tone-2": "🧎🏻", + "kneeling_person::skin-tone-3": "🧎🏼", + "kneeling_person::skin-tone-4": "🧎🏽", + "kneeling_person::skin-tone-5": "🧎🏾", + "kneeling_person::skin-tone-6": "🧎🏿", + "man_kneeling": "🧎♂️", + "man_kneeling::skin-tone-2": "🧎🏻♂️", + "man_kneeling::skin-tone-3": "🧎🏼♂️", + "man_kneeling::skin-tone-4": "🧎🏽♂️", + "man_kneeling::skin-tone-5": "🧎🏾♂️", + "man_kneeling::skin-tone-6": "🧎🏿♂️", + "woman_kneeling": "🧎♀️", + "woman_kneeling::skin-tone-2": "🧎🏻♀️", + "woman_kneeling::skin-tone-3": "🧎🏼♀️", + "woman_kneeling::skin-tone-4": "🧎🏽♀️", + "woman_kneeling::skin-tone-5": "🧎🏾♀️", + "woman_kneeling::skin-tone-6": "🧎🏿♀️", + "person_with_probing_cane": "🧑🦯", + "person_with_probing_cane::skin-tone-2": "🧑🏻🦯", + "person_with_probing_cane::skin-tone-3": "🧑🏼🦯", + "person_with_probing_cane::skin-tone-4": "🧑🏽🦯", + "person_with_probing_cane::skin-tone-5": "🧑🏾🦯", + "person_with_probing_cane::skin-tone-6": "🧑🏿🦯", + "man_with_probing_cane": "👨🦯", + "man_with_probing_cane::skin-tone-2": "👨🏻🦯", + "man_with_probing_cane::skin-tone-3": "👨🏼🦯", + "man_with_probing_cane::skin-tone-4": "👨🏽🦯", + "man_with_probing_cane::skin-tone-5": "👨🏾🦯", + "man_with_probing_cane::skin-tone-6": "👨🏿🦯", + "woman_with_probing_cane": "👩🦯", + "woman_with_probing_cane::skin-tone-2": "👩🏻🦯", + "woman_with_probing_cane::skin-tone-3": "👩🏼🦯", + "woman_with_probing_cane::skin-tone-4": "👩🏽🦯", + "woman_with_probing_cane::skin-tone-5": "👩🏾🦯", + "woman_with_probing_cane::skin-tone-6": "👩🏿🦯", + "person_in_motorized_wheelchair": "🧑🦼", + "person_in_motorized_wheelchair::skin-tone-2": "🧑🏻🦼", + "person_in_motorized_wheelchair::skin-tone-3": "🧑🏼🦼", + "person_in_motorized_wheelchair::skin-tone-4": "🧑🏽🦼", + "person_in_motorized_wheelchair::skin-tone-5": "🧑🏾🦼", + "person_in_motorized_wheelchair::skin-tone-6": "🧑🏿🦼", + "man_in_motorized_wheelchair": "👨🦼", + "man_in_motorized_wheelchair::skin-tone-2": "👨🏻🦼", + "man_in_motorized_wheelchair::skin-tone-3": "👨🏼🦼", + "man_in_motorized_wheelchair::skin-tone-4": "👨🏽🦼", + "man_in_motorized_wheelchair::skin-tone-5": "👨🏾🦼", + "man_in_motorized_wheelchair::skin-tone-6": "👨🏿🦼", + "woman_in_motorized_wheelchair": "👩🦼", + "woman_in_motorized_wheelchair::skin-tone-2": "👩🏻🦼", + "woman_in_motorized_wheelchair::skin-tone-3": "👩🏼🦼", + "woman_in_motorized_wheelchair::skin-tone-4": "👩🏽🦼", + "woman_in_motorized_wheelchair::skin-tone-5": "👩🏾🦼", + "woman_in_motorized_wheelchair::skin-tone-6": "👩🏿🦼", + "person_in_manual_wheelchair": "🧑🦽", + "person_in_manual_wheelchair::skin-tone-2": "🧑🏻🦽", + "person_in_manual_wheelchair::skin-tone-3": "🧑🏼🦽", + "person_in_manual_wheelchair::skin-tone-4": "🧑🏽🦽", + "person_in_manual_wheelchair::skin-tone-5": "🧑🏾🦽", + "person_in_manual_wheelchair::skin-tone-6": "🧑🏿🦽", + "man_in_manual_wheelchair": "👨🦽", + "man_in_manual_wheelchair::skin-tone-2": "👨🏻🦽", + "man_in_manual_wheelchair::skin-tone-3": "👨🏼🦽", + "man_in_manual_wheelchair::skin-tone-4": "👨🏽🦽", + "man_in_manual_wheelchair::skin-tone-5": "👨🏾🦽", + "man_in_manual_wheelchair::skin-tone-6": "👨🏿🦽", + "woman_in_manual_wheelchair": "👩🦽", + "woman_in_manual_wheelchair::skin-tone-2": "👩🏻🦽", + "woman_in_manual_wheelchair::skin-tone-3": "👩🏼🦽", + "woman_in_manual_wheelchair::skin-tone-4": "👩🏽🦽", + "woman_in_manual_wheelchair::skin-tone-5": "👩🏾🦽", + "woman_in_manual_wheelchair::skin-tone-6": "👩🏿🦽", + "runner": "🏃", + "running": "🏃", + "runner::skin-tone-2": "🏃🏻", + "running::skin-tone-2": "🏃🏻", + "runner::skin-tone-3": "🏃🏼", + "running::skin-tone-3": "🏃🏼", + "runner::skin-tone-4": "🏃🏽", + "running::skin-tone-4": "🏃🏽", + "runner::skin-tone-5": "🏃🏾", + "running::skin-tone-5": "🏃🏾", + "runner::skin-tone-6": "🏃🏿", + "running::skin-tone-6": "🏃🏿", + "man-running": "🏃♂️", + "man-running::skin-tone-2": "🏃🏻♂️", + "man-running::skin-tone-3": "🏃🏼♂️", + "man-running::skin-tone-4": "🏃🏽♂️", + "man-running::skin-tone-5": "🏃🏾♂️", + "man-running::skin-tone-6": "🏃🏿♂️", + "woman-running": "🏃♀️", + "woman-running::skin-tone-2": "🏃🏻♀️", + "woman-running::skin-tone-3": "🏃🏼♀️", + "woman-running::skin-tone-4": "🏃🏽♀️", + "woman-running::skin-tone-5": "🏃🏾♀️", + "woman-running::skin-tone-6": "🏃🏿♀️", + "dancer": "💃", + "dancer::skin-tone-2": "💃🏻", + "dancer::skin-tone-3": "💃🏼", + "dancer::skin-tone-4": "💃🏽", + "dancer::skin-tone-5": "💃🏾", + "dancer::skin-tone-6": "💃🏿", + "man_dancing": "🕺", + "man_dancing::skin-tone-2": "🕺🏻", + "man_dancing::skin-tone-3": "🕺🏼", + "man_dancing::skin-tone-4": "🕺🏽", + "man_dancing::skin-tone-5": "🕺🏾", + "man_dancing::skin-tone-6": "🕺🏿", + "man_in_business_suit_levitating": "🕴️", + "man_in_business_suit_levitating::skin-tone-2": "🕴🏻", + "man_in_business_suit_levitating::skin-tone-3": "🕴🏼", + "man_in_business_suit_levitating::skin-tone-4": "🕴🏽", + "man_in_business_suit_levitating::skin-tone-5": "🕴🏾", + "man_in_business_suit_levitating::skin-tone-6": "🕴🏿", + "dancers": "👯", + "men-with-bunny-ears-partying": "👯♂️", + "man-with-bunny-ears-partying": "👯♂️", + "women-with-bunny-ears-partying": "👯♀️", + "woman-with-bunny-ears-partying": "👯♀️", + "person_in_steamy_room": "🧖", + "person_in_steamy_room::skin-tone-2": "🧖🏻", + "person_in_steamy_room::skin-tone-3": "🧖🏼", + "person_in_steamy_room::skin-tone-4": "🧖🏽", + "person_in_steamy_room::skin-tone-5": "🧖🏾", + "person_in_steamy_room::skin-tone-6": "🧖🏿", + "man_in_steamy_room": "🧖♂️", + "man_in_steamy_room::skin-tone-2": "🧖🏻♂️", + "man_in_steamy_room::skin-tone-3": "🧖🏼♂️", + "man_in_steamy_room::skin-tone-4": "🧖🏽♂️", + "man_in_steamy_room::skin-tone-5": "🧖🏾♂️", + "man_in_steamy_room::skin-tone-6": "🧖🏿♂️", + "woman_in_steamy_room": "🧖♀️", + "woman_in_steamy_room::skin-tone-2": "🧖🏻♀️", + "woman_in_steamy_room::skin-tone-3": "🧖🏼♀️", + "woman_in_steamy_room::skin-tone-4": "🧖🏽♀️", + "woman_in_steamy_room::skin-tone-5": "🧖🏾♀️", + "woman_in_steamy_room::skin-tone-6": "🧖🏿♀️", + "person_climbing": "🧗", + "person_climbing::skin-tone-2": "🧗🏻", + "person_climbing::skin-tone-3": "🧗🏼", + "person_climbing::skin-tone-4": "🧗🏽", + "person_climbing::skin-tone-5": "🧗🏾", + "person_climbing::skin-tone-6": "🧗🏿", + "man_climbing": "🧗♂️", + "man_climbing::skin-tone-2": "🧗🏻♂️", + "man_climbing::skin-tone-3": "🧗🏼♂️", + "man_climbing::skin-tone-4": "🧗🏽♂️", + "man_climbing::skin-tone-5": "🧗🏾♂️", + "man_climbing::skin-tone-6": "🧗🏿♂️", + "woman_climbing": "🧗♀️", + "woman_climbing::skin-tone-2": "🧗🏻♀️", + "woman_climbing::skin-tone-3": "🧗🏼♀️", + "woman_climbing::skin-tone-4": "🧗🏽♀️", + "woman_climbing::skin-tone-5": "🧗🏾♀️", + "woman_climbing::skin-tone-6": "🧗🏿♀️", + "fencer": "🤺", + "horse_racing": "🏇", + "horse_racing::skin-tone-2": "🏇🏻", + "horse_racing::skin-tone-3": "🏇🏼", + "horse_racing::skin-tone-4": "🏇🏽", + "horse_racing::skin-tone-5": "🏇🏾", + "horse_racing::skin-tone-6": "🏇🏿", + "skier": "⛷️", + "snowboarder": "🏂", + "snowboarder::skin-tone-2": "🏂🏻", + "snowboarder::skin-tone-3": "🏂🏼", + "snowboarder::skin-tone-4": "🏂🏽", + "snowboarder::skin-tone-5": "🏂🏾", + "snowboarder::skin-tone-6": "🏂🏿", + "golfer": "🏌️", + "golfer::skin-tone-2": "🏌🏻", + "golfer::skin-tone-3": "🏌🏼", + "golfer::skin-tone-4": "🏌🏽", + "golfer::skin-tone-5": "🏌🏾", + "golfer::skin-tone-6": "🏌🏿", + "man-golfing": "🏌️♂️", + "man-golfing::skin-tone-2": "🏌🏻♂️", + "man-golfing::skin-tone-3": "🏌🏼♂️", + "man-golfing::skin-tone-4": "🏌🏽♂️", + "man-golfing::skin-tone-5": "🏌🏾♂️", + "man-golfing::skin-tone-6": "🏌🏿♂️", + "woman-golfing": "🏌️♀️", + "woman-golfing::skin-tone-2": "🏌🏻♀️", + "woman-golfing::skin-tone-3": "🏌🏼♀️", + "woman-golfing::skin-tone-4": "🏌🏽♀️", + "woman-golfing::skin-tone-5": "🏌🏾♀️", + "woman-golfing::skin-tone-6": "🏌🏿♀️", + "surfer": "🏄", + "surfer::skin-tone-2": "🏄🏻", + "surfer::skin-tone-3": "🏄🏼", + "surfer::skin-tone-4": "🏄🏽", + "surfer::skin-tone-5": "🏄🏾", + "surfer::skin-tone-6": "🏄🏿", + "man-surfing": "🏄♂️", + "man-surfing::skin-tone-2": "🏄🏻♂️", + "man-surfing::skin-tone-3": "🏄🏼♂️", + "man-surfing::skin-tone-4": "🏄🏽♂️", + "man-surfing::skin-tone-5": "🏄🏾♂️", + "man-surfing::skin-tone-6": "🏄🏿♂️", + "woman-surfing": "🏄♀️", + "woman-surfing::skin-tone-2": "🏄🏻♀️", + "woman-surfing::skin-tone-3": "🏄🏼♀️", + "woman-surfing::skin-tone-4": "🏄🏽♀️", + "woman-surfing::skin-tone-5": "🏄🏾♀️", + "woman-surfing::skin-tone-6": "🏄🏿♀️", + "rowboat": "🚣", + "rowboat::skin-tone-2": "🚣🏻", + "rowboat::skin-tone-3": "🚣🏼", + "rowboat::skin-tone-4": "🚣🏽", + "rowboat::skin-tone-5": "🚣🏾", + "rowboat::skin-tone-6": "🚣🏿", + "man-rowing-boat": "🚣♂️", + "man-rowing-boat::skin-tone-2": "🚣🏻♂️", + "man-rowing-boat::skin-tone-3": "🚣🏼♂️", + "man-rowing-boat::skin-tone-4": "🚣🏽♂️", + "man-rowing-boat::skin-tone-5": "🚣🏾♂️", + "man-rowing-boat::skin-tone-6": "🚣🏿♂️", + "woman-rowing-boat": "🚣♀️", + "woman-rowing-boat::skin-tone-2": "🚣🏻♀️", + "woman-rowing-boat::skin-tone-3": "🚣🏼♀️", + "woman-rowing-boat::skin-tone-4": "🚣🏽♀️", + "woman-rowing-boat::skin-tone-5": "🚣🏾♀️", + "woman-rowing-boat::skin-tone-6": "🚣🏿♀️", + "swimmer": "🏊", + "swimmer::skin-tone-2": "🏊🏻", + "swimmer::skin-tone-3": "🏊🏼", + "swimmer::skin-tone-4": "🏊🏽", + "swimmer::skin-tone-5": "🏊🏾", + "swimmer::skin-tone-6": "🏊🏿", + "man-swimming": "🏊♂️", + "man-swimming::skin-tone-2": "🏊🏻♂️", + "man-swimming::skin-tone-3": "🏊🏼♂️", + "man-swimming::skin-tone-4": "🏊🏽♂️", + "man-swimming::skin-tone-5": "🏊🏾♂️", + "man-swimming::skin-tone-6": "🏊🏿♂️", + "woman-swimming": "🏊♀️", + "woman-swimming::skin-tone-2": "🏊🏻♀️", + "woman-swimming::skin-tone-3": "🏊🏼♀️", + "woman-swimming::skin-tone-4": "🏊🏽♀️", + "woman-swimming::skin-tone-5": "🏊🏾♀️", + "woman-swimming::skin-tone-6": "🏊🏿♀️", + "person_with_ball": "⛹️", + "person_with_ball::skin-tone-2": "⛹🏻", + "person_with_ball::skin-tone-3": "⛹🏼", + "person_with_ball::skin-tone-4": "⛹🏽", + "person_with_ball::skin-tone-5": "⛹🏾", + "person_with_ball::skin-tone-6": "⛹🏿", + "man-bouncing-ball": "⛹️♂️", + "man-bouncing-ball::skin-tone-2": "⛹🏻♂️", + "man-bouncing-ball::skin-tone-3": "⛹🏼♂️", + "man-bouncing-ball::skin-tone-4": "⛹🏽♂️", + "man-bouncing-ball::skin-tone-5": "⛹🏾♂️", + "man-bouncing-ball::skin-tone-6": "⛹🏿♂️", + "woman-bouncing-ball": "⛹️♀️", + "woman-bouncing-ball::skin-tone-2": "⛹🏻♀️", + "woman-bouncing-ball::skin-tone-3": "⛹🏼♀️", + "woman-bouncing-ball::skin-tone-4": "⛹🏽♀️", + "woman-bouncing-ball::skin-tone-5": "⛹🏾♀️", + "woman-bouncing-ball::skin-tone-6": "⛹🏿♀️", + "weight_lifter": "🏋️", + "weight_lifter::skin-tone-2": "🏋🏻", + "weight_lifter::skin-tone-3": "🏋🏼", + "weight_lifter::skin-tone-4": "🏋🏽", + "weight_lifter::skin-tone-5": "🏋🏾", + "weight_lifter::skin-tone-6": "🏋🏿", + "man-lifting-weights": "🏋️♂️", + "man-lifting-weights::skin-tone-2": "🏋🏻♂️", + "man-lifting-weights::skin-tone-3": "🏋🏼♂️", + "man-lifting-weights::skin-tone-4": "🏋🏽♂️", + "man-lifting-weights::skin-tone-5": "🏋🏾♂️", + "man-lifting-weights::skin-tone-6": "🏋🏿♂️", + "woman-lifting-weights": "🏋️♀️", + "woman-lifting-weights::skin-tone-2": "🏋🏻♀️", + "woman-lifting-weights::skin-tone-3": "🏋🏼♀️", + "woman-lifting-weights::skin-tone-4": "🏋🏽♀️", + "woman-lifting-weights::skin-tone-5": "🏋🏾♀️", + "woman-lifting-weights::skin-tone-6": "🏋🏿♀️", + "bicyclist": "🚴", + "bicyclist::skin-tone-2": "🚴🏻", + "bicyclist::skin-tone-3": "🚴🏼", + "bicyclist::skin-tone-4": "🚴🏽", + "bicyclist::skin-tone-5": "🚴🏾", + "bicyclist::skin-tone-6": "🚴🏿", + "man-biking": "🚴♂️", + "man-biking::skin-tone-2": "🚴🏻♂️", + "man-biking::skin-tone-3": "🚴🏼♂️", + "man-biking::skin-tone-4": "🚴🏽♂️", + "man-biking::skin-tone-5": "🚴🏾♂️", + "man-biking::skin-tone-6": "🚴🏿♂️", + "woman-biking": "🚴♀️", + "woman-biking::skin-tone-2": "🚴🏻♀️", + "woman-biking::skin-tone-3": "🚴🏼♀️", + "woman-biking::skin-tone-4": "🚴🏽♀️", + "woman-biking::skin-tone-5": "🚴🏾♀️", + "woman-biking::skin-tone-6": "🚴🏿♀️", + "mountain_bicyclist": "🚵", + "mountain_bicyclist::skin-tone-2": "🚵🏻", + "mountain_bicyclist::skin-tone-3": "🚵🏼", + "mountain_bicyclist::skin-tone-4": "🚵🏽", + "mountain_bicyclist::skin-tone-5": "🚵🏾", + "mountain_bicyclist::skin-tone-6": "🚵🏿", + "man-mountain-biking": "🚵♂️", + "man-mountain-biking::skin-tone-2": "🚵🏻♂️", + "man-mountain-biking::skin-tone-3": "🚵🏼♂️", + "man-mountain-biking::skin-tone-4": "🚵🏽♂️", + "man-mountain-biking::skin-tone-5": "🚵🏾♂️", + "man-mountain-biking::skin-tone-6": "🚵🏿♂️", + "woman-mountain-biking": "🚵♀️", + "woman-mountain-biking::skin-tone-2": "🚵🏻♀️", + "woman-mountain-biking::skin-tone-3": "🚵🏼♀️", + "woman-mountain-biking::skin-tone-4": "🚵🏽♀️", + "woman-mountain-biking::skin-tone-5": "🚵🏾♀️", + "woman-mountain-biking::skin-tone-6": "🚵🏿♀️", + "person_doing_cartwheel": "🤸", + "person_doing_cartwheel::skin-tone-2": "🤸🏻", + "person_doing_cartwheel::skin-tone-3": "🤸🏼", + "person_doing_cartwheel::skin-tone-4": "🤸🏽", + "person_doing_cartwheel::skin-tone-5": "🤸🏾", + "person_doing_cartwheel::skin-tone-6": "🤸🏿", + "man-cartwheeling": "🤸♂️", + "man-cartwheeling::skin-tone-2": "🤸🏻♂️", + "man-cartwheeling::skin-tone-3": "🤸🏼♂️", + "man-cartwheeling::skin-tone-4": "🤸🏽♂️", + "man-cartwheeling::skin-tone-5": "🤸🏾♂️", + "man-cartwheeling::skin-tone-6": "🤸🏿♂️", + "woman-cartwheeling": "🤸♀️", + "woman-cartwheeling::skin-tone-2": "🤸🏻♀️", + "woman-cartwheeling::skin-tone-3": "🤸🏼♀️", + "woman-cartwheeling::skin-tone-4": "🤸🏽♀️", + "woman-cartwheeling::skin-tone-5": "🤸🏾♀️", + "woman-cartwheeling::skin-tone-6": "🤸🏿♀️", + "wrestlers": "🤼", + "man-wrestling": "🤼♂️", + "woman-wrestling": "🤼♀️", + "water_polo": "🤽", + "water_polo::skin-tone-2": "🤽🏻", + "water_polo::skin-tone-3": "🤽🏼", + "water_polo::skin-tone-4": "🤽🏽", + "water_polo::skin-tone-5": "🤽🏾", + "water_polo::skin-tone-6": "🤽🏿", + "man-playing-water-polo": "🤽♂️", + "man-playing-water-polo::skin-tone-2": "🤽🏻♂️", + "man-playing-water-polo::skin-tone-3": "🤽🏼♂️", + "man-playing-water-polo::skin-tone-4": "🤽🏽♂️", + "man-playing-water-polo::skin-tone-5": "🤽🏾♂️", + "man-playing-water-polo::skin-tone-6": "🤽🏿♂️", + "woman-playing-water-polo": "🤽♀️", + "woman-playing-water-polo::skin-tone-2": "🤽🏻♀️", + "woman-playing-water-polo::skin-tone-3": "🤽🏼♀️", + "woman-playing-water-polo::skin-tone-4": "🤽🏽♀️", + "woman-playing-water-polo::skin-tone-5": "🤽🏾♀️", + "woman-playing-water-polo::skin-tone-6": "🤽🏿♀️", + "handball": "🤾", + "handball::skin-tone-2": "🤾🏻", + "handball::skin-tone-3": "🤾🏼", + "handball::skin-tone-4": "🤾🏽", + "handball::skin-tone-5": "🤾🏾", + "handball::skin-tone-6": "🤾🏿", + "man-playing-handball": "🤾♂️", + "man-playing-handball::skin-tone-2": "🤾🏻♂️", + "man-playing-handball::skin-tone-3": "🤾🏼♂️", + "man-playing-handball::skin-tone-4": "🤾🏽♂️", + "man-playing-handball::skin-tone-5": "🤾🏾♂️", + "man-playing-handball::skin-tone-6": "🤾🏿♂️", + "woman-playing-handball": "🤾♀️", + "woman-playing-handball::skin-tone-2": "🤾🏻♀️", + "woman-playing-handball::skin-tone-3": "🤾🏼♀️", + "woman-playing-handball::skin-tone-4": "🤾🏽♀️", + "woman-playing-handball::skin-tone-5": "🤾🏾♀️", + "woman-playing-handball::skin-tone-6": "🤾🏿♀️", + "juggling": "🤹", + "juggling::skin-tone-2": "🤹🏻", + "juggling::skin-tone-3": "🤹🏼", + "juggling::skin-tone-4": "🤹🏽", + "juggling::skin-tone-5": "🤹🏾", + "juggling::skin-tone-6": "🤹🏿", + "man-juggling": "🤹♂️", + "man-juggling::skin-tone-2": "🤹🏻♂️", + "man-juggling::skin-tone-3": "🤹🏼♂️", + "man-juggling::skin-tone-4": "🤹🏽♂️", + "man-juggling::skin-tone-5": "🤹🏾♂️", + "man-juggling::skin-tone-6": "🤹🏿♂️", + "woman-juggling": "🤹♀️", + "woman-juggling::skin-tone-2": "🤹🏻♀️", + "woman-juggling::skin-tone-3": "🤹🏼♀️", + "woman-juggling::skin-tone-4": "🤹🏽♀️", + "woman-juggling::skin-tone-5": "🤹🏾♀️", + "woman-juggling::skin-tone-6": "🤹🏿♀️", + "person_in_lotus_position": "🧘", + "person_in_lotus_position::skin-tone-2": "🧘🏻", + "person_in_lotus_position::skin-tone-3": "🧘🏼", + "person_in_lotus_position::skin-tone-4": "🧘🏽", + "person_in_lotus_position::skin-tone-5": "🧘🏾", + "person_in_lotus_position::skin-tone-6": "🧘🏿", + "man_in_lotus_position": "🧘♂️", + "man_in_lotus_position::skin-tone-2": "🧘🏻♂️", + "man_in_lotus_position::skin-tone-3": "🧘🏼♂️", + "man_in_lotus_position::skin-tone-4": "🧘🏽♂️", + "man_in_lotus_position::skin-tone-5": "🧘🏾♂️", + "man_in_lotus_position::skin-tone-6": "🧘🏿♂️", + "woman_in_lotus_position": "🧘♀️", + "woman_in_lotus_position::skin-tone-2": "🧘🏻♀️", + "woman_in_lotus_position::skin-tone-3": "🧘🏼♀️", + "woman_in_lotus_position::skin-tone-4": "🧘🏽♀️", + "woman_in_lotus_position::skin-tone-5": "🧘🏾♀️", + "woman_in_lotus_position::skin-tone-6": "🧘🏿♀️", + "bath": "🛀", + "bath::skin-tone-2": "🛀🏻", + "bath::skin-tone-3": "🛀🏼", + "bath::skin-tone-4": "🛀🏽", + "bath::skin-tone-5": "🛀🏾", + "bath::skin-tone-6": "🛀🏿", + "sleeping_accommodation": "🛌", + "sleeping_accommodation::skin-tone-2": "🛌🏻", + "sleeping_accommodation::skin-tone-3": "🛌🏼", + "sleeping_accommodation::skin-tone-4": "🛌🏽", + "sleeping_accommodation::skin-tone-5": "🛌🏾", + "sleeping_accommodation::skin-tone-6": "🛌🏿", + "people_holding_hands": "🧑🤝🧑", + "people_holding_hands::skin-tone-2": "🧑🏻🤝🧑🏻", + "people_holding_hands::skin-tone-3": "🧑🏼🤝🧑🏼", + "people_holding_hands::skin-tone-4": "🧑🏽🤝🧑🏽", + "people_holding_hands::skin-tone-5": "🧑🏾🤝🧑🏾", + "people_holding_hands::skin-tone-6": "🧑🏿🤝🧑🏿", + "two_women_holding_hands": "👭", + "women_holding_hands": "👭", + "two_women_holding_hands::skin-tone-2": "👭🏻", + "women_holding_hands::skin-tone-2": "👭🏻", + "two_women_holding_hands::skin-tone-3": "👭🏼", + "women_holding_hands::skin-tone-3": "👭🏼", + "two_women_holding_hands::skin-tone-4": "👭🏽", + "women_holding_hands::skin-tone-4": "👭🏽", + "two_women_holding_hands::skin-tone-5": "👭🏾", + "women_holding_hands::skin-tone-5": "👭🏾", + "two_women_holding_hands::skin-tone-6": "👭🏿", + "women_holding_hands::skin-tone-6": "👭🏿", + "man_and_woman_holding_hands": "👫", + "couple": "👫", + "man_and_woman_holding_hands::skin-tone-2": "👫🏻", + "couple::skin-tone-2": "👫🏻", + "man_and_woman_holding_hands::skin-tone-3": "👫🏼", + "couple::skin-tone-3": "👫🏼", + "man_and_woman_holding_hands::skin-tone-4": "👫🏽", + "couple::skin-tone-4": "👫🏽", + "man_and_woman_holding_hands::skin-tone-5": "👫🏾", + "couple::skin-tone-5": "👫🏾", + "man_and_woman_holding_hands::skin-tone-6": "👫🏿", + "couple::skin-tone-6": "👫🏿", + "two_men_holding_hands": "👬", + "men_holding_hands": "👬", + "two_men_holding_hands::skin-tone-2": "👬🏻", + "men_holding_hands::skin-tone-2": "👬🏻", + "two_men_holding_hands::skin-tone-3": "👬🏼", + "men_holding_hands::skin-tone-3": "👬🏼", + "two_men_holding_hands::skin-tone-4": "👬🏽", + "men_holding_hands::skin-tone-4": "👬🏽", + "two_men_holding_hands::skin-tone-5": "👬🏾", + "men_holding_hands::skin-tone-5": "👬🏾", + "two_men_holding_hands::skin-tone-6": "👬🏿", + "men_holding_hands::skin-tone-6": "👬🏿", + "couplekiss": "💏", + "couplekiss::skin-tone-2": "💏🏻", + "couplekiss::skin-tone-3": "💏🏼", + "couplekiss::skin-tone-4": "💏🏽", + "couplekiss::skin-tone-5": "💏🏾", + "couplekiss::skin-tone-6": "💏🏿", + "woman-kiss-man": "👩❤️💋👨", + "woman-kiss-man::skin-tone-2": "👩🏻❤️💋👨🏻", + "woman-kiss-man::skin-tone-3": "👩🏼❤️💋👨🏼", + "woman-kiss-man::skin-tone-4": "👩🏽❤️💋👨🏽", + "woman-kiss-man::skin-tone-5": "👩🏾❤️💋👨🏾", + "woman-kiss-man::skin-tone-6": "👩🏿❤️💋👨🏿", + "man-kiss-man": "👨❤️💋👨", + "man-kiss-man::skin-tone-2": "👨🏻❤️💋👨🏻", + "man-kiss-man::skin-tone-3": "👨🏼❤️💋👨🏼", + "man-kiss-man::skin-tone-4": "👨🏽❤️💋👨🏽", + "man-kiss-man::skin-tone-5": "👨🏾❤️💋👨🏾", + "man-kiss-man::skin-tone-6": "👨🏿❤️💋👨🏿", + "woman-kiss-woman": "👩❤️💋👩", + "woman-kiss-woman::skin-tone-2": "👩🏻❤️💋👩🏻", + "woman-kiss-woman::skin-tone-3": "👩🏼❤️💋👩🏼", + "woman-kiss-woman::skin-tone-4": "👩🏽❤️💋👩🏽", + "woman-kiss-woman::skin-tone-5": "👩🏾❤️💋👩🏾", + "woman-kiss-woman::skin-tone-6": "👩🏿❤️💋👩🏿", + "couple_with_heart": "💑", + "couple_with_heart::skin-tone-2": "💑🏻", + "couple_with_heart::skin-tone-3": "💑🏼", + "couple_with_heart::skin-tone-4": "💑🏽", + "couple_with_heart::skin-tone-5": "💑🏾", + "couple_with_heart::skin-tone-6": "💑🏿", + "woman-heart-man": "👩❤️👨", + "woman-heart-man::skin-tone-2": "👩🏻❤️👨🏻", + "woman-heart-man::skin-tone-3": "👩🏼❤️👨🏼", + "woman-heart-man::skin-tone-4": "👩🏽❤️👨🏽", + "woman-heart-man::skin-tone-5": "👩🏾❤️👨🏾", + "woman-heart-man::skin-tone-6": "👩🏿❤️👨🏿", + "man-heart-man": "👨❤️👨", + "man-heart-man::skin-tone-2": "👨🏻❤️👨🏻", + "man-heart-man::skin-tone-3": "👨🏼❤️👨🏼", + "man-heart-man::skin-tone-4": "👨🏽❤️👨🏽", + "man-heart-man::skin-tone-5": "👨🏾❤️👨🏾", + "man-heart-man::skin-tone-6": "👨🏿❤️👨🏿", + "woman-heart-woman": "👩❤️👩", + "woman-heart-woman::skin-tone-2": "👩🏻❤️👩🏻", + "woman-heart-woman::skin-tone-3": "👩🏼❤️👩🏼", + "woman-heart-woman::skin-tone-4": "👩🏽❤️👩🏽", + "woman-heart-woman::skin-tone-5": "👩🏾❤️👩🏾", + "woman-heart-woman::skin-tone-6": "👩🏿❤️👩🏿", + "family": "👪", + "man-woman-boy": "👨👩👦", + "man-woman-girl": "👨👩👧", + "man-woman-girl-boy": "👨👩👧👦", + "man-woman-boy-boy": "👨👩👦👦", + "man-woman-girl-girl": "👨👩👧👧", + "man-man-boy": "👨👨👦", + "man-man-girl": "👨👨👧", + "man-man-girl-boy": "👨👨👧👦", + "man-man-boy-boy": "👨👨👦👦", + "man-man-girl-girl": "👨👨👧👧", + "woman-woman-boy": "👩👩👦", + "woman-woman-girl": "👩👩👧", + "woman-woman-girl-boy": "👩👩👧👦", + "woman-woman-boy-boy": "👩👩👦👦", + "woman-woman-girl-girl": "👩👩👧👧", + "man-boy": "👨👦", + "man-boy-boy": "👨👦👦", + "man-girl": "👨👧", + "man-girl-boy": "👨👧👦", + "man-girl-girl": "👨👧👧", + "woman-boy": "👩👦", + "woman-boy-boy": "👩👦👦", + "woman-girl": "👩👧", + "woman-girl-boy": "👩👧👦", + "woman-girl-girl": "👩👧👧", + "speaking_head_in_silhouette": "🗣️", + "bust_in_silhouette": "👤", + "busts_in_silhouette": "👥", + "people_hugging": "🫂", + "footprints": "👣", + "monkey_face": "🐵", + "monkey": "🐒", + "gorilla": "🦍", + "orangutan": "🦧", + "dog": "🐶", + "dog2": "🐕", + "guide_dog": "🦮", + "service_dog": "🐕🦺", + "poodle": "🐩", + "wolf": "🐺", + "fox_face": "🦊", + "raccoon": "🦝", + "cat": "🐱", + "cat2": "🐈", + "black_cat": "🐈⬛", + "lion_face": "🦁", + "tiger": "🐯", + "tiger2": "🐅", + "leopard": "🐆", + "horse": "🐴", + "racehorse": "🐎", + "unicorn_face": "🦄", + "zebra_face": "🦓", + "deer": "🦌", + "bison": "🦬", + "cow": "🐮", + "ox": "🐂", + "water_buffalo": "🐃", + "cow2": "🐄", + "pig": "🐷", + "pig2": "🐖", + "boar": "🐗", + "pig_nose": "🐽", + "ram": "🐏", + "sheep": "🐑", + "goat": "🐐", + "dromedary_camel": "🐪", + "camel": "🐫", + "llama": "🦙", + "giraffe_face": "🦒", + "elephant": "🐘", + "mammoth": "🦣", + "rhinoceros": "🦏", + "hippopotamus": "🦛", + "mouse": "🐭", + "mouse2": "🐁", + "rat": "🐀", + "hamster": "🐹", + "rabbit": "🐰", + "rabbit2": "🐇", + "chipmunk": "🐿️", + "beaver": "🦫", + "hedgehog": "🦔", + "bat": "🦇", + "bear": "🐻", + "polar_bear": "🐻❄️", + "koala": "🐨", + "panda_face": "🐼", + "sloth": "🦥", + "otter": "🦦", + "skunk": "🦨", + "kangaroo": "🦘", + "badger": "🦡", + "feet": "🐾", + "paw_prints": "🐾", + "turkey": "🦃", + "chicken": "🐔", + "rooster": "🐓", + "hatching_chick": "🐣", + "baby_chick": "🐤", + "hatched_chick": "🐥", + "bird": "🐦", + "penguin": "🐧", + "dove_of_peace": "🕊️", + "eagle": "🦅", + "duck": "🦆", + "swan": "🦢", + "owl": "🦉", + "dodo": "🦤", + "feather": "🪶", + "flamingo": "🦩", + "peacock": "🦚", + "parrot": "🦜", + "frog": "🐸", + "crocodile": "🐊", + "turtle": "🐢", + "lizard": "🦎", + "snake": "🐍", + "dragon_face": "🐲", + "dragon": "🐉", + "sauropod": "🦕", + "t-rex": "🦖", + "philosoraptor": "🦖", + "whale": "🐳", + "whale2": "🐋", + "dolphin": "🐬", + "flipper": "🐬", + "seal": "🦭", + "fish": "🐟", + "tropical_fish": "🐠", + "blowfish": "🐡", + "shark": "🦈", + "octopus": "🐙", + "shell": "🐚", + "coral": "🪸", + "snail": "🐌", + "butterfly": "🦋", + "bug": "🐛", + "ant": "🐜", + "bee": "🐝", + "honeybee": "🐝", + "beetle": "🪲", + "ladybug": "🐞", + "lady_beetle": "🐞", + "cricket": "🦗", + "cockroach": "🪳", + "spider": "🕷️", + "spider_web": "🕸️", + "scorpion": "🦂", + "mosquito": "🦟", + "fly": "🪰", + "worm": "🪱", + "microbe": "🦠", + "bouquet": "💐", + "cherry_blossom": "🌸", + "white_flower": "💮", + "lotus": "🪷", + "rosette": "🏵️", + "rose": "🌹", + "wilted_flower": "🥀", + "hibiscus": "🌺", + "sunflower": "🌻", + "blossom": "🌼", + "tulip": "🌷", + "seedling": "🌱", + "potted_plant": "🪴", + "evergreen_tree": "🌲", + "deciduous_tree": "🌳", + "palm_tree": "🌴", + "cactus": "🌵", + "ear_of_rice": "🌾", + "herb": "🌿", + "shamrock": "☘️", + "four_leaf_clover": "🍀", + "maple_leaf": "🍁", + "fallen_leaf": "🍂", + "leaves": "🍃", + "empty_nest": "🪹", + "nest_with_eggs": "🪺", + "grapes": "🍇", + "melon": "🍈", + "watermelon": "🍉", + "tangerine": "🍊", + "lemon": "🍋", + "banana": "🍌", + "pineapple": "🍍", + "mango": "🥭", + "apple": "🍎", + "green_apple": "🍏", + "pear": "🍐", + "peach": "🍑", + "cherries": "🍒", + "strawberry": "🍓", + "blueberries": "🫐", + "kiwifruit": "🥝", + "tomato": "🍅", + "olive": "🫒", + "coconut": "🥥", + "avocado": "🥑", + "eggplant": "🍆", + "potato": "🥔", + "carrot": "🥕", + "corn": "🌽", + "hot_pepper": "🌶️", + "bell_pepper": "🫑", + "cucumber": "🥒", + "leafy_green": "🥬", + "broccoli": "🥦", + "garlic": "🧄", + "onion": "🧅", + "mushroom": "🍄", + "peanuts": "🥜", + "beans": "🫘", + "chestnut": "🌰", + "bread": "🍞", + "croissant": "🥐", + "baguette_bread": "🥖", + "flatbread": "🫓", + "pretzel": "🥨", + "bagel": "🥯", + "pancakes": "🥞", + "waffle": "🧇", + "cheese_wedge": "🧀", + "meat_on_bone": "🍖", + "poultry_leg": "🍗", + "cut_of_meat": "🥩", + "bacon": "🥓", + "hamburger": "🍔", + "fries": "🍟", + "pizza": "🍕", + "hotdog": "🌭", + "sandwich": "🥪", + "taco": "🌮", + "burrito": "🌯", + "tamale": "🫔", + "stuffed_flatbread": "🥙", + "falafel": "🧆", + "egg": "🥚", + "fried_egg": "🍳", + "cooking": "🍳", + "shallow_pan_of_food": "🥘", + "stew": "🍲", + "fondue": "🫕", + "bowl_with_spoon": "🥣", + "green_salad": "🥗", + "popcorn": "🍿", + "butter": "🧈", + "salt": "🧂", + "canned_food": "🥫", + "bento": "🍱", + "rice_cracker": "🍘", + "rice_ball": "🍙", + "rice": "🍚", + "curry": "🍛", + "ramen": "🍜", + "spaghetti": "🍝", + "sweet_potato": "🍠", + "oden": "🍢", + "sushi": "🍣", + "fried_shrimp": "🍤", + "fish_cake": "🍥", + "moon_cake": "🥮", + "dango": "🍡", + "dumpling": "🥟", + "fortune_cookie": "🥠", + "takeout_box": "🥡", + "crab": "🦀", + "lobster": "🦞", + "shrimp": "🦐", + "squid": "🦑", + "oyster": "🦪", + "icecream": "🍦", + "shaved_ice": "🍧", + "ice_cream": "🍨", + "doughnut": "🍩", + "cookie": "🍪", + "birthday": "🎂", + "cake": "🍰", + "cupcake": "🧁", + "pie": "🥧", + "chocolate_bar": "🍫", + "candy": "🍬", + "lollipop": "🍭", + "custard": "🍮", + "honey_pot": "🍯", + "baby_bottle": "🍼", + "glass_of_milk": "🥛", + "coffee": "☕", + "teapot": "🫖", + "tea": "🍵", + "sake": "🍶", + "champagne": "🍾", + "wine_glass": "🍷", + "cocktail": "🍸", + "tropical_drink": "🍹", + "beer": "🍺", + "beers": "🍻", + "clinking_glasses": "🥂", + "tumbler_glass": "🥃", + "pouring_liquid": "🫗", + "cup_with_straw": "🥤", + "bubble_tea": "🧋", + "beverage_box": "🧃", + "mate_drink": "🧉", + "ice_cube": "🧊", + "chopsticks": "🥢", + "knife_fork_plate": "🍽️", + "fork_and_knife": "🍴", + "spoon": "🥄", + "hocho": "🔪", + "knife": "🔪", + "jar": "🫙", + "amphora": "🏺", + "earth_africa": "🌍", + "earth_americas": "🌎", + "earth_asia": "🌏", + "globe_with_meridians": "🌐", + "world_map": "🗺️", + "japan": "🗾", + "compass": "🧭", + "snow_capped_mountain": "🏔️", + "mountain": "⛰️", + "volcano": "🌋", + "mount_fuji": "🗻", + "camping": "🏕️", + "beach_with_umbrella": "🏖️", + "desert": "🏜️", + "desert_island": "🏝️", + "national_park": "🏞️", + "stadium": "🏟️", + "classical_building": "🏛️", + "building_construction": "🏗️", + "bricks": "🧱", + "databricks": "🧱", + "pydata": "🪨", + "rock": "🪨", + "wood": "🪵", + "hut": "🛖", + "house_buildings": "🏘️", + "derelict_house_building": "🏚️", + "house": "🏠", + "house_with_garden": "🏡", + "office": "🏢", + "post_office": "🏣", + "european_post_office": "🏤", + "hospital": "🏥", + "bank": "🏦", + "hotel": "🏨", + "love_hotel": "🏩", + "convenience_store": "🏪", + "school": "🏫", + "department_store": "🏬", + "factory": "🏭", + "japanese_castle": "🏯", + "european_castle": "🏰", + "wedding": "💒", + "tokyo_tower": "🗼", + "statue_of_liberty": "🗽", + "church": "⛪", + "mosque": "🕌", + "hindu_temple": "🛕", + "synagogue": "🕍", + "shinto_shrine": "⛩️", + "kaaba": "🕋", + "fountain": "⛲", + "tent": "⛺", + "foggy": "🌁", + "night_with_stars": "🌃", + "cityscape": "🏙️", + "sunrise_over_mountains": "🌄", + "sunrise": "🌅", + "city_sunset": "🌆", + "city_sunrise": "🌇", + "bridge_at_night": "🌉", + "hotsprings": "♨️", + "carousel_horse": "🎠", + "playground_slide": "🛝", + "ferris_wheel": "🎡", + "roller_coaster": "🎢", + "barber": "💈", + "circus_tent": "🎪", + "steam_locomotive": "🚂", + "railway_car": "🚃", + "bullettrain_side": "🚄", + "bullettrain_front": "🚅", + "train2": "🚆", + "metro": "🚇", + "light_rail": "🚈", + "station": "🚉", + "tram": "🚊", + "monorail": "🚝", + "mountain_railway": "🚞", + "train": "🚋", + "bus": "🚌", + "oncoming_bus": "🚍", + "trolleybus": "🚎", + "minibus": "🚐", + "ambulance": "🚑", + "fire_engine": "🚒", + "police_car": "🚓", + "oncoming_police_car": "🚔", + "taxi": "🚕", + "oncoming_taxi": "🚖", + "car": "🚗", + "red_car": "🚗", + "oncoming_automobile": "🚘", + "blue_car": "🚙", + "pickup_truck": "🛻", + "truck": "🚚", + "articulated_lorry": "🚛", + "tractor": "🚜", + "racing_car": "🏎️", + "racing_motorcycle": "🏍️", + "motor_scooter": "🛵", + "manual_wheelchair": "🦽", + "motorized_wheelchair": "🦼", + "auto_rickshaw": "🛺", + "bike": "🚲", + "scooter": "🛴", + "skateboard": "🛹", + "roller_skate": "🛼", + "busstop": "🚏", + "motorway": "🛣️", + "railway_track": "🛤️", + "oil_drum": "🛢️", + "fuelpump": "⛽", + "wheel": "🛞", + "rotating_light": "🚨", + "traffic_light": "🚥", + "vertical_traffic_light": "🚦", + "octagonal_sign": "🛑", + "construction": "🚧", + "anchor": "⚓", + "ring_buoy": "🛟", + "boat": "⛵", + "sailboat": "⛵", + "canoe": "🛶", + "speedboat": "🚤", + "passenger_ship": "🛳️", + "ferry": "⛴️", + "motor_boat": "🛥️", + "ship": "🚢", + "airplane": "✈️", + "small_airplane": "🛩️", + "airplane_departure": "🛫", + "airplane_arriving": "🛬", + "parachute": "🪂", + "seat": "💺", + "helicopter": "🚁", + "suspension_railway": "🚟", + "mountain_cableway": "🚠", + "aerial_tramway": "🚡", + "satellite": "🛰️", + "rocket": "🚀", + "rocketing": "🚀", + "rocking": "🚀", + "ahhhhhhhhh": "🚀", + "flying_saucer": "🛸", + "bellhop_bell": "🛎️", + "luggage": "🧳", + "hourglass": "⌛", + "hourglass_flowing_sand": "⏳", + "watch": "⌚", + "alarm_clock": "⏰", + "stopwatch": "⏱️", + "timer_clock": "⏲️", + "mantelpiece_clock": "🕰️", + "clock12": "🕛", + "clock1230": "🕧", + "clock1": "🕐", + "clock130": "🕜", + "clock2": "🕑", + "clock230": "🕝", + "clock3": "🕒", + "clock330": "🕞", + "clock4": "🕓", + "clock430": "🕟", + "clock5": "🕔", + "clock530": "🕠", + "clock6": "🕕", + "clock630": "🕡", + "clock7": "🕖", + "clock730": "🕢", + "clock8": "🕗", + "clock830": "🕣", + "clock9": "🕘", + "clock930": "🕤", + "clock10": "🕙", + "clock1030": "🕥", + "clock11": "🕚", + "clock1130": "🕦", + "new_moon": "🌑", + "waxing_crescent_moon": "🌒", + "first_quarter_moon": "🌓", + "moon": "🌔", + "waxing_gibbous_moon": "🌔", + "full_moon": "🌕", + "waning_gibbous_moon": "🌖", + "last_quarter_moon": "🌗", + "waning_crescent_moon": "🌘", + "crescent_moon": "🌙", + "new_moon_with_face": "🌚", + "first_quarter_moon_with_face": "🌛", + "last_quarter_moon_with_face": "🌜", + "thermometer": "🌡️", + "sunny": "☀️", + "full_moon_with_face": "🌝", + "sun_with_face": "🌞", + "ringed_planet": "🪐", + "star": "⭐", + "star2": "🌟", + "stars": "🌠", + "milky_way": "🌌", + "cloud": "☁️", + "partly_sunny": "⛅", + "thunder_cloud_and_rain": "⛈️", + "mostly_sunny": "🌤️", + "sun_small_cloud": "🌤️", + "barely_sunny": "🌥️", + "sun_behind_cloud": "🌥️", + "partly_sunny_rain": "🌦️", + "sun_behind_rain_cloud": "🌦️", + "rain_cloud": "🌧️", + "snow_cloud": "🌨️", + "lightning": "🌩️", + "lightning_cloud": "🌩️", + "tornado": "🌪️", + "tornado_cloud": "🌪️", + "fog": "🌫️", + "wind_blowing_face": "🌬️", + "cyclone": "🌀", + "rainbow": "🌈", + "rainbow-daggy": "🌈", + "closed_umbrella": "🌂", + "umbrella": "☂️", + "umbrella_with_rain_drops": "☔", + "umbrella_on_ground": "⛱️", + "zap": "⚡", + "snowflake": "❄️", + "snowman": "☃️", + "snowman_without_snow": "⛄", + "comet": "☄️", + "fire": "🔥", + "tuzki_onfire": "🔥", + "tuzki-onfire": "🔥", + "droplet": "💧", + "ocean": "🌊", + "jack_o_lantern": "🎃", + "christmas_tree": "🎄", + "fireworks": "🎆", + "sparkler": "🎇", + "firecracker": "🧨", + "sparkles": "✨", + "balloon": "🎈", + "tada": "🎉", + "confetti_ball": "🎊", + "tanabata_tree": "🎋", + "bamboo": "🎍", + "dolls": "🎎", + "flags": "🎏", + "wind_chime": "🎐", + "rice_scene": "🎑", + "red_envelope": "🧧", + "ribbon": "🎀", + "gift": "🎁", + "reminder_ribbon": "🎗️", + "admission_tickets": "🎟️", + "ticket": "🎫", + "medal": "🎖️", + "trophy": "🏆", + "sports_medal": "🏅", + "first_place_medal": "🥇", + "second_place_medal": "🥈", + "third_place_medal": "🥉", + "soccer": "⚽", + "baseball": "⚾", + "softball": "🥎", + "basketball": "🏀", + "volleyball": "🏐", + "football": "🏈", + "rugby_football": "🏉", + "tennis": "🎾", + "flying_disc": "🥏", + "bowling": "🎳", + "cricket_bat_and_ball": "🏏", + "field_hockey_stick_and_ball": "🏑", + "ice_hockey_stick_and_puck": "🏒", + "lacrosse": "🥍", + "table_tennis_paddle_and_ball": "🏓", + "badminton_racquet_and_shuttlecock": "🏸", + "boxing_glove": "🥊", + "martial_arts_uniform": "🥋", + "goal_net": "🥅", + "golf": "⛳", + "ice_skate": "⛸️", + "fishing_pole_and_fish": "🎣", + "diving_mask": "🤿", + "running_shirt_with_sash": "🎽", + "ski": "🎿", + "sled": "🛷", + "curling_stone": "🥌", + "dart": "🎯", + "yo-yo": "🪀", + "kite": "🪁", + "8ball": "🎱", + "crystal_ball": "🔮", + "magic_wand": "🪄", + "nazar_amulet": "🧿", + "hamsa": "🪬", + "video_game": "🎮", + "joystick": "🕹️", + "slot_machine": "🎰", + "game_die": "🎲", + "jigsaw": "🧩", + "teddy_bear": "🧸", + "pinata": "🪅", + "mirror_ball": "🪩", + "nesting_dolls": "🪆", + "spades": "♠️", + "hearts": "♥️", + "diamonds": "♦️", + "clubs": "♣️", + "chess_pawn": "♟️", + "black_joker": "🃏", + "mahjong": "🀄", + "flower_playing_cards": "🎴", + "performing_arts": "🎭", + "frame_with_picture": "🖼️", + "art": "🎨", + "thread": "🧵", + "sewing_needle": "🪡", + "yarn": "🧶", + "knot": "🪢", + "eyeglasses": "👓", + "dark_sunglasses": "🕶️", + "goggles": "🥽", + "lab_coat": "🥼", + "safety_vest": "🦺", + "necktie": "👔", + "shirt": "👕", + "tshirt": "👕", + "jeans": "👖", + "scarf": "🧣", + "gloves": "🧤", + "coat": "🧥", + "socks": "🧦", + "dress": "👗", + "kimono": "👘", + "sari": "🥻", + "one-piece_swimsuit": "🩱", + "briefs": "🩲", + "shorts": "🩳", + "bikini": "👙", + "womans_clothes": "👚", + "purse": "👛", + "handbag": "👜", + "pouch": "👝", + "shopping_bags": "🛍️", + "school_satchel": "🎒", + "thong_sandal": "🩴", + "mans_shoe": "👞", + "shoe": "👞", + "athletic_shoe": "👟", + "hiking_boot": "🥾", + "womans_flat_shoe": "🥿", + "high_heel": "👠", + "sandal": "👡", + "ballet_shoes": "🩰", + "boot": "👢", + "crown": "👑", + "womans_hat": "👒", + "tophat": "🎩", + "mortar_board": "🎓", + "billed_cap": "🧢", + "military_helmet": "🪖", + "helmet_with_white_cross": "⛑️", + "prayer_beads": "📿", + "lipstick": "💄", + "ring": "💍", + "gem": "💎", + "mute": "🔇", + "speaker": "🔈", + "sound": "🔉", + "loud_sound": "🔊", + "loudspeaker": "📢", + "mega": "📣", + "postal_horn": "📯", + "bell": "🔔", + "no_bell": "🔕", + "musical_score": "🎼", + "musical_note": "🎵", + "notes": "🎶", + "studio_microphone": "🎙️", + "level_slider": "🎚️", + "control_knobs": "🎛️", + "microphone": "🎤", + "headphones": "🎧", + "radio": "📻", + "saxophone": "🎷", + "accordion": "🪗", + "guitar": "🎸", + "musical_keyboard": "🎹", + "trumpet": "🎺", + "violin": "🎻", + "banjo": "🪕", + "drum_with_drumsticks": "🥁", + "long_drum": "🪘", + "iphone": "📱", + "calling": "📲", + "phone": "☎️", + "telephone": "☎️", + "telephone_receiver": "📞", + "pager": "📟", + "fax": "📠", + "battery": "🔋", + "low_battery": "🪫", + "electric_plug": "🔌", + "computer": "💻", + "desktop_computer": "🖥️", + "printer": "🖨️", + "keyboard": "⌨️", + "three_button_mouse": "🖱️", + "trackball": "🖲️", + "minidisc": "💽", + "floppy_disk": "💾", + "cd": "💿", + "dvd": "📀", + "abacus": "🧮", + "movie_camera": "🎥", + "film_frames": "🎞️", + "film_projector": "📽️", + "webassembly": ".wasm", + "clapper": "🎬", + "tv": "📺", + "camera": "📷", + "camera_with_flash": "📸", + "video_camera": "📹", + "vhs": "📼", + "mag": "🔍", + "mag_right": "🔎", + "candle": "🕯️", + "bulb": "💡", + "flashlight": "🔦", + "izakaya_lantern": "🏮", + "lantern": "🏮", + "diya_lamp": "🪔", + "notebook_with_decorative_cover": "📔", + "closed_book": "📕", + "book": "📖", + "open_book": "📖", + "green_book": "📗", + "blue_book": "📘", + "orange_book": "📙", + "books": "📚", + "notebook": "📓", + "ledger": "📒", + "page_with_curl": "📃", + "scroll": "📜", + "page_facing_up": "📄", + "newspaper": "📰", + "rolled_up_newspaper": "🗞️", + "bookmark_tabs": "📑", + "bookmark": "🔖", + "label": "🏷️", + "moneybag": "💰", + "coin": "🪙", + "yen": "💴", + "dollar": "💵", + "euro": "💶", + "pound": "💷", + "money_with_wings": "💸", + "money_flying": "💸", + "credit_card": "💳", + "receipt": "🧾", + "chart": "💹", + "email": "✉️", + "envelope": "✉️", + "e-mail": "📧", + "incoming_envelope": "📨", + "envelope_with_arrow": "📩", + "outbox_tray": "📤", + "inbox_tray": "📥", + "package": "📦", + "mailbox": "📫", + "mailbox_closed": "📪", + "mailbox_with_mail": "📬", + "mailbox_with_no_mail": "📭", + "postbox": "📮", + "ballot_box_with_ballot": "🗳️", + "pencil2": "✏️", + "black_nib": "✒️", + "lower_left_fountain_pen": "🖋️", + "lower_left_ballpoint_pen": "🖊️", + "lower_left_paintbrush": "🖌️", + "lower_left_crayon": "🖍️", + "memo": "📝", + "pencil": "📝", + "briefcase": "💼", + "file_folder": "📁", + "open_file_folder": "📂", + "card_index_dividers": "🗂️", + "date": "📅", + "calendar": "📆", + "spiral_note_pad": "🗒️", + "spiral_calendar_pad": "🗓️", + "card_index": "📇", + "chart_with_upwards_trend": "📈", + "chart_with_downwards_trend": "📉", + "bar_chart": "📊", + "clipboard": "📋", + "pushpin": "📌", + "kodee_pin": "📌", + "kodee-pin": "📌", + "round_pushpin": "📍", + "paperclip": "📎", + "linked_paperclips": "🖇️", + "straight_ruler": "📏", + "triangular_ruler": "📐", + "scissors": "✂️", + "card_file_box": "🗃️", + "file_cabinet": "🗄️", + "wastebasket": "🗑️", + "lock": "🔒", + "unlock": "🔓", + "lock_with_ink_pen": "🔏", + "closed_lock_with_key": "🔐", + "key": "🔑", + "old_key": "🗝️", + "hammer": "🔨", + "axe": "🪓", + "pick": "⛏️", + "hammer_and_pick": "⚒️", + "hammer_and_wrench": "🛠️", + "dagger_knife": "🗡️", + "crossed_swords": "⚔️", + "gun": "🔫", + "boomerang": "🪃", + "bow_and_arrow": "🏹", + "shield": "🛡️", + "carpentry_saw": "🪚", + "wrench": "🔧", + "screwdriver": "🪛", + "nut_and_bolt": "🔩", + "gear": "⚙️", + "compression": "🗜️", + "scales": "⚖️", + "probing_cane": "🦯", + "link": "🔗", + "chains": "⛓️", + "hook": "🪝", + "toolbox": "🧰", + "magnet": "🧲", + "ladder": "🪜", + "alembic": "⚗️", + "test_tube": "🧪", + "petri_dish": "🧫", + "dna": "🧬", + "microscope": "🔬", + "telescope": "🔭", + "satellite_antenna": "📡", + "syringe": "💉", + "drop_of_blood": "🩸", + "pill": "💊", + "adhesive_bandage": "🩹", + "crutch": "🩼", + "stethoscope": "🩺", + "x-ray": "🩻", + "door": "🚪", + "elevator": "🛗", + "mirror": "🪞", + "window": "🪟", + "bed": "🛏️", + "couch_and_lamp": "🛋️", + "chair": "🪑", + "toilet": "🚽", + "plunger": "🪠", + "shower": "🚿", + "bathtub": "🛁", + "mouse_trap": "🪤", + "razor": "🪒", + "lotion_bottle": "🧴", + "safety_pin": "🧷", + "broom": "🧹", + "basket": "🧺", + "roll_of_paper": "🧻", + "bucket": "🪣", + "soap": "🧼", + "bubbles": "🫧", + "toothbrush": "🪥", + "sponge": "🧽", + "fire_extinguisher": "🧯", + "shopping_trolley": "🛒", + "smoking": "🚬", + "coffin": "⚰️", + "headstone": "🪦", + "funeral_urn": "⚱️", + "moyai": "🗿", + "placard": "🪧", + "identification_card": "🪪", + "atm": "🏧", + "put_litter_in_its_place": "🚮", + "potable_water": "🚰", + "wheelchair": "♿", + "mens": "🚹", + "womens": "🚺", + "restroom": "🚻", + "baby_symbol": "🚼", + "wc": "🚾", + "passport_control": "🛂", + "customs": "🛃", + "baggage_claim": "🛄", + "left_luggage": "🛅", + "warning": "⚠️", + "children_crossing": "🚸", + "no_entry": "⛔", + "no_entry_sign": "🚫", + "no_bicycles": "🚳", + "no_smoking": "🚭", + "do_not_litter": "🚯", + "non-potable_water": "🚱", + "no_pedestrians": "🚷", + "no_mobile_phones": "📵", + "underage": "🔞", + "radioactive_sign": "☢️", + "biohazard_sign": "☣️", + "arrow_up": "⬆️", + "arrow_upper_right": "↗️", + "arrow_right": "➡️", + "arrow_lower_right": "↘️", + "arrow_down": "⬇️", + "arrow_lower_left": "↙️", + "arrow_left": "⬅️", + "arrow_upper_left": "↖️", + "arrow_up_down": "↕️", + "left_right_arrow": "↔️", + "leftwards_arrow_with_hook": "↩️", + "arrow_right_hook": "↪️", + "arrow_heading_up": "⤴️", + "arrow_heading_down": "⤵️", + "arrows_clockwise": "🔃", + "arrows_counterclockwise": "🔄", + "back": "🔙", + "end": "🔚", + "on": "🔛", + "soon": "🔜", + "top": "🔝", + "place_of_worship": "🛐", + "atom_symbol": "⚛️", + "om_symbol": "🕉️", + "star_of_david": "✡️", + "wheel_of_dharma": "☸️", + "yin_yang": "☯️", + "latin_cross": "✝️", + "orthodox_cross": "☦️", + "star_and_crescent": "☪️", + "peace_symbol": "☮️", + "menorah_with_nine_branches": "🕎", + "six_pointed_star": "🔯", + "aries": "♈", + "taurus": "♉", + "gemini": "♊", + "cancer": "♋", + "leo": "♌", + "virgo": "♍", + "libra": "♎", + "scorpius": "♏", + "sagittarius": "♐", + "capricorn": "♑", + "aquarius": "♒", + "pisces": "♓", + "ophiuchus": "⛎", + "twisted_rightwards_arrows": "🔀", + "repeat": "🔁", + "repeat_one": "🔂", + "arrow_forward": "▶️", + "fast_forward": "⏩", + "black_right_pointing_double_triangle_with_vertical_bar": "⏭️", + "black_right_pointing_triangle_with_double_vertical_bar": "⏯️", + "arrow_backward": "◀️", + "rewind": "⏪", + "black_left_pointing_double_triangle_with_vertical_bar": "⏮️", + "arrow_up_small": "🔼", + "arrow_double_up": "⏫", + "arrow_down_small": "🔽", + "arrow_double_down": "⏬", + "double_vertical_bar": "⏸️", + "black_square_for_stop": "⏹️", + "black_circle_for_record": "⏺️", + "eject": "⏏️", + "cinema": "🎦", + "low_brightness": "🔅", + "high_brightness": "🔆", + "signal_strength": "📶", + "vibration_mode": "📳", + "mobile_phone_off": "📴", + "female_sign": "♀️", + "male_sign": "♂️", + "transgender_symbol": "⚧️", + "heavy_multiplication_x": "✖️", + "heavy_plus_sign": "➕", + "heavy_minus_sign": "➖", + "heavy_division_sign": "➗", + "heavy_equals_sign": "🟰", + "infinity": "♾️", + "bangbang": "‼️", + "interrobang": "⁉️", + "question": "❓", + "grey_question": "❔", + "grey_exclamation": "❕", + "exclamation": "❗", + "heavy_exclamation_mark": "❗", + "wavy_dash": "〰️", + "currency_exchange": "💱", + "heavy_dollar_sign": "💲", + "medical_symbol": "⚕️", + "staff_of_aesculapius": "⚕️", + "recycle": "♻️", + "fleur_de_lis": "⚜️", + "trident": "🔱", + "name_badge": "📛", + "beginner": "🔰", + "o": "⭕", + "white_check_mark": "✅", + "ballot_box_with_check": "☑️", + "heavy_check_mark": "✔️", + "x": "❌", + "negative_squared_cross_mark": "❎", + "curly_loop": "➰", + "loop": "➿", + "part_alternation_mark": "〽️", + "eight_spoked_asterisk": "✳️", + "eight_pointed_black_star": "✴️", + "sparkle": "❇️", + "copyright": "©️", + "registered": "®️", + "tm": "™️", + "hash": "#️⃣", + "keycap_star": "*️⃣", + "zero": "0️⃣", + "one": "1️⃣", + "two": "2️⃣", + "three": "3️⃣", + "four": "4️⃣", + "five": "5️⃣", + "six": "6️⃣", + "seven": "7️⃣", + "eight": "8️⃣", + "nine": "9️⃣", + "keycap_ten": "🔟", + "capital_abcd": "🔠", + "fivetran": "5️⃣", + "abcd": "🔡", + "symbols": "🔣", + "abc": "🔤", + "a": "🅰️", + "ab": "🆎", + "b": "🅱️", + "cl": "🆑", + "cool": "🆒", + "free": "🆓", + "information_source": "ℹ️", + "id": "🆔", + "m": "Ⓜ️", + "new": "🆕", + "ng": "🆖", + "o2": "🅾️", + "ok": "🆗", + "parking": "🅿️", + "sos": "🆘", + "up": "🆙", + "vs": "🆚", + "koko": "🈁", + "sa": "🈂️", + "u6708": "🈷️", + "u6709": "🈶", + "u6307": "🈯", + "ideograph_advantage": "🉐", + "u5272": "🈹", + "u7121": "🈚", + "u7981": "🈲", + "accept": "🉑", + "u7533": "🈸", + "u5408": "🈴", + "u7a7a": "🈳", + "congratulations": "㊗️", + "secret": "㊙️", + "u55b6": "🈺", + "u6e80": "🈵", + "red_circle": "🔴", + "large_orange_circle": "🟠", + "large_yellow_circle": "🟡", + "large_green_circle": "🟢", + "large_blue_circle": "🔵", + "large_purple_circle": "🟣", + "large_brown_circle": "🟤", + "black_circle": "⚫", + "white_circle": "⚪", + "large_red_square": "🟥", + "large_orange_square": "🟧", + "large_yellow_square": "🟨", + "large_green_square": "🟩", + "large_blue_square": "🟦", + "large_purple_square": "🟪", + "large_brown_square": "🟫", + "black_large_square": "⬛", + "white_large_square": "⬜", + "black_medium_square": "◼️", + "white_medium_square": "◻️", + "black_medium_small_square": "◾", + "white_medium_small_square": "◽", + "black_small_square": "▪️", + "white_small_square": "▫️", + "large_orange_diamond": "🔶", + "large_blue_diamond": "🔷", + "small_orange_diamond": "🔸", + "small_blue_diamond": "🔹", + "small_red_triangle": "🔺", + "small_red_triangle_down": "🔻", + "diamond_shape_with_a_dot_inside": "💠", + "radio_button": "🔘", + "white_square_button": "🔳", + "black_square_button": "🔲", + "checkered_flag": "🏁", + "triangular_flag_on_post": "🚩", + "crossed_flags": "🎌", + "waving_black_flag": "🏴", + "waving_white_flag": "🏳️", + "rainbow-flag": "🏳️🌈", + "transgender_flag": "🏳️⚧️", + "pirate_flag": "🏴☠️", + "flag-ac": "🇦🇨", + "flag-ad": "🇦🇩", + "flag-ae": "🇦🇪", + "flag-af": "🇦🇫", + "flag-ag": "🇦🇬", + "flag-ai": "🇦🇮", + "flag-al": "🇦🇱", + "flag-am": "🇦🇲", + "flag-ao": "🇦🇴", + "flag-aq": "🇦🇶", + "flag-ar": "🇦🇷", + "flag-as": "🇦🇸", + "flag-at": "🇦🇹", + "flag-au": "🇦🇺", + "flag-aw": "🇦🇼", + "flag-ax": "🇦🇽", + "flag-az": "🇦🇿", + "flag-ba": "🇧🇦", + "flag-bb": "🇧🇧", + "flag-bd": "🇧🇩", + "flag-be": "🇧🇪", + "flag-bf": "🇧🇫", + "flag-bg": "🇧🇬", + "flag-bh": "🇧🇭", + "flag-bi": "🇧🇮", + "flag-bj": "🇧🇯", + "flag-bl": "🇧🇱", + "flag-bm": "🇧🇲", + "flag-bn": "🇧🇳", + "flag-bo": "🇧🇴", + "flag-bq": "🇧🇶", + "flag-br": "🇧🇷", + "flag-bs": "🇧🇸", + "flag-bt": "🇧🇹", + "flag-bv": "🇧🇻", + "flag-bw": "🇧🇼", + "flag-by": "🇧🇾", + "flag-bz": "🇧🇿", + "flag-ca": "🇨🇦", + "flag-cc": "🇨🇨", + "flag-cd": "🇨🇩", + "flag-cf": "🇨🇫", + "flag-cg": "🇨🇬", + "flag-ch": "🇨🇭", + "flag-ci": "🇨🇮", + "flag-ck": "🇨🇰", + "flag-cl": "🇨🇱", + "flag-cm": "🇨🇲", + "cn": "🇨🇳", + "flag-cn": "🇨🇳", + "flag-co": "🇨🇴", + "flag-cp": "🇨🇵", + "flag-cr": "🇨🇷", + "flag-cu": "🇨🇺", + "flag-cv": "🇨🇻", + "flag-cw": "🇨🇼", + "flag-cx": "🇨🇽", + "flag-cy": "🇨🇾", + "flag-cz": "🇨🇿", + "de": "🇩🇪", + "flag-de": "🇩🇪", + "flag-dg": "🇩🇬", + "flag-dj": "🇩🇯", + "flag-dk": "🇩🇰", + "flag-dm": "🇩🇲", + "flag-do": "🇩🇴", + "flag-dz": "🇩🇿", + "flag-ea": "🇪🇦", + "flag-ec": "🇪🇨", + "flag-ee": "🇪🇪", + "flag-eg": "🇪🇬", + "flag-eh": "🇪🇭", + "flag-er": "🇪🇷", + "es": "🇪🇸", + "flag-es": "🇪🇸", + "flag-et": "🇪🇹", + "flag-eu": "🇪🇺", + "flag-fi": "🇫🇮", + "flag-fj": "🇫🇯", + "flag-fk": "🇫🇰", + "flag-fm": "🇫🇲", + "flag-fo": "🇫🇴", + "fr": "🇫🇷", + "flag-fr": "🇫🇷", + "flag-ga": "🇬🇦", + "gb": "🇬🇧", + "flag-gb": "🇬🇧", + "flag-gd": "🇬🇩", + "flag-ge": "🇬🇪", + "flag-gf": "🇬🇫", + "flag-gg": "🇬🇬", + "flag-gh": "🇬🇭", + "flag-gi": "🇬🇮", + "flag-gl": "🇬🇱", + "flag-gm": "🇬🇲", + "flag-gn": "🇬🇳", + "flag-gp": "🇬🇵", + "flag-gq": "🇬🇶", + "flag-gr": "🇬🇷", + "flag-gs": "🇬🇸", + "flag-gt": "🇬🇹", + "flag-gu": "🇬🇺", + "flag-gw": "🇬🇼", + "flag-gy": "🇬🇾", + "flag-hk": "🇭🇰", + "flag-hm": "🇭🇲", + "flag-hn": "🇭🇳", + "flag-hr": "🇭🇷", + "flag-ht": "🇭🇹", + "flag-hu": "🇭🇺", + "flag-ic": "🇮🇨", + "flag-id": "🇮🇩", + "flag-ie": "🇮🇪", + "flag-il": "🇮🇱", + "flag-im": "🇮🇲", + "flag-in": "🇮🇳", + "flag-io": "🇮🇴", + "flag-iq": "🇮🇶", + "flag-ir": "🇮🇷", + "flag-is": "🇮🇸", + "it": "🇮🇹", + "flag-it": "🇮🇹", + "flag-je": "🇯🇪", + "flag-jm": "🇯🇲", + "flag-jo": "🇯🇴", + "jp": "🇯🇵", + "flag-jp": "🇯🇵", + "flag-ke": "🇰🇪", + "flag-kg": "🇰🇬", + "flag-kh": "🇰🇭", + "flag-ki": "🇰🇮", + "flag-km": "🇰🇲", + "flag-kn": "🇰🇳", + "flag-kp": "🇰🇵", + "kr": "🇰🇷", + "flag-kr": "🇰🇷", + "flag-kw": "🇰🇼", + "flag-ky": "🇰🇾", + "flag-kz": "🇰🇿", + "flag-la": "🇱🇦", + "flag-lb": "🇱🇧", + "flag-lc": "🇱🇨", + "flag-li": "🇱🇮", + "flag-lk": "🇱🇰", + "flag-lr": "🇱🇷", + "flag-ls": "🇱🇸", + "flag-lt": "🇱🇹", + "flag-lu": "🇱🇺", + "flag-lv": "🇱🇻", + "flag-ly": "🇱🇾", + "flag-ma": "🇲🇦", + "flag-mc": "🇲🇨", + "flag-md": "🇲🇩", + "flag-me": "🇲🇪", + "flag-mf": "🇲🇫", + "flag-mg": "🇲🇬", + "flag-mh": "🇲🇭", + "flag-mk": "🇲🇰", + "flag-ml": "🇲🇱", + "flag-mm": "🇲🇲", + "flag-mn": "🇲🇳", + "flag-mo": "🇲🇴", + "flag-mp": "🇲🇵", + "flag-mq": "🇲🇶", + "flag-mr": "🇲🇷", + "flag-ms": "🇲🇸", + "flag-mt": "🇲🇹", + "flag-mu": "🇲🇺", + "flag-mv": "🇲🇻", + "flag-mw": "🇲🇼", + "flag-mx": "🇲🇽", + "flag-my": "🇲🇾", + "flag-mz": "🇲🇿", + "flag-na": "🇳🇦", + "flag-nc": "🇳🇨", + "flag-ne": "🇳🇪", + "flag-nf": "🇳🇫", + "flag-ng": "🇳🇬", + "flag-ni": "🇳🇮", + "flag-nl": "🇳🇱", + "flag-no": "🇳🇴", + "flag-np": "🇳🇵", + "flag-nr": "🇳🇷", + "flag-nu": "🇳🇺", + "flag-nz": "🇳🇿", + "flag-om": "🇴🇲", + "flag-pa": "🇵🇦", + "flag-pe": "🇵🇪", + "flag-pf": "🇵🇫", + "flag-pg": "🇵🇬", + "flag-ph": "🇵🇭", + "flag-pk": "🇵🇰", + "flag-pl": "🇵🇱", + "flag-pm": "🇵🇲", + "flag-pn": "🇵🇳", + "flag-pr": "🇵🇷", + "flag-ps": "🇵🇸", + "flag-pt": "🇵🇹", + "flag-pw": "🇵🇼", + "flag-py": "🇵🇾", + "flag-qa": "🇶🇦", + "flag-re": "🇷🇪", + "flag-ro": "🇷🇴", + "flag-rs": "🇷🇸", + "ru": "🇷🇺", + "flag-ru": "🇷🇺", + "flag-rw": "🇷🇼", + "flag-sa": "🇸🇦", + "flag-sb": "🇸🇧", + "flag-sc": "🇸🇨", + "flag-sd": "🇸🇩", + "flag-se": "🇸🇪", + "flag-sg": "🇸🇬", + "flag-sh": "🇸🇭", + "flag-si": "🇸🇮", + "flag-sj": "🇸🇯", + "flag-sk": "🇸🇰", + "flag-sl": "🇸🇱", + "flag-sm": "🇸🇲", + "flag-sn": "🇸🇳", + "flag-so": "🇸🇴", + "flag-sr": "🇸🇷", + "flag-ss": "🇸🇸", + "flag-st": "🇸🇹", + "flag-sv": "🇸🇻", + "flag-sx": "🇸🇽", + "flag-sy": "🇸🇾", + "flag-sz": "🇸🇿", + "flag-ta": "🇹🇦", + "flag-tc": "🇹🇨", + "flag-td": "🇹🇩", + "flag-tf": "🇹🇫", + "flag-tg": "🇹🇬", + "flag-th": "🇹🇭", + "flag-tj": "🇹🇯", + "flag-tk": "🇹🇰", + "flag-tl": "🇹🇱", + "flag-tm": "🇹🇲", + "flag-tn": "🇹🇳", + "flag-to": "🇹🇴", + "flag-tr": "🇹🇷", + "flag-tt": "🇹🇹", + "flag-tv": "🇹🇻", + "flag-tw": "🇹🇼", + "flag-tz": "🇹🇿", + "flag-ua": "🇺🇦", + "flag-ug": "🇺🇬", + "flag-um": "🇺🇲", + "flag-un": "🇺🇳", + "us": "🇺🇸", + "flag-us": "🇺🇸", + "flag-uy": "🇺🇾", + "flag-uz": "🇺🇿", + "flag-va": "🇻🇦", + "flag-vc": "🇻🇨", + "flag-ve": "🇻🇪", + "flag-vg": "🇻🇬", + "flag-vi": "🇻🇮", + "flag-vn": "🇻🇳", + "flag-vu": "🇻🇺", + "flag-wf": "🇼🇫", + "flag-ws": "🇼🇸", + "flag-xk": "🇽🇰", + "flag-ye": "🇾🇪", + "flag-yt": "🇾🇹", + "flag-za": "🇿🇦", + "flag-zm": "🇿🇲", + "flag-zw": "🇿🇼", + "flag-england": "🏴", + "flag-scotland": "🏴", + "flag-wales": "🏴", + "kotlinconf23": "K", + "kotlinconf-2023": "K", + "kotlin-intensifies-purple": "K", + "kotlin-intensifies": "K", + "compose-multiplatform": "K", + "kotlinnew": "K", + "kotlin": "K", + "kotlin_emoji": "K", + "kotlin-emoji": "K", + "kotlin-gradient": "K", + "kotlin_gradient": "K", + "kotlin-golf": "🏌️", + "mascot": "🧸", + "mascot-wink": "🧸", + "compose": "✏️", + "yes": "👌", + "ohyes": "👌", + "thank-you": "🙏", + "tnx": "🙏", + "cool-doge": "🐕", + "blob-hype": "🦠", + "suspend": "⏸️", + "parrot-upside-down": "🦜", + "twitter": "T", + "javascript": "JS", + "this-is-fine": "🙂", + "marrrcin": "🙂", + "partydagster": "🎉", + "kedroid-party": "🎉", + "planet-daggy": "🪐", + "flames-daggy": "🔥", + "next-level-daggy": "👌", + "awesome": "👌", + "dagster": "D", + "super": "👌", + "blob_ok_hand": "👌", + "blob-okay-hand": "👌", + "gradle": "G", + "maskot-wink": "🧸", + "maskot": "🧸", + "kotlin-flag": "K", + "oh-yeah": "🎉", + "thumbsup_all": "👍", + "not-kotlin": "😶", + "trollface": "🧌", + "no": "🚫", + "ohno": "🚫", + "nospam": "🚫", + "blob_shrug": "🤷", + "thread-please": "🧵", + "dagster-bot-resolve": "🤖", + "bananadance": "🍌", + "bananas": "🍌", + "laptop_parrot": "🦜", + "laptop-parrot": "🦜", + "dancing_parrot": "🦜", + "dancing-parrot": "🦜", + "bongo_blob": "🦠", + "blob": "🦠", + "blob_wave": "👋", + "pikachu_wave": "👋", + "android_wave": "👋", + "android-wave": "👋", + "dagster-bot-surfaced-to-issue": "🤖", + "tsow-slack-icon": "S", + "kedro": "K", + "pipe": "|", + "prefect": "P", + "snowflake-inc": "❄️", + "kestra": "K" +}
\ No newline at end of file diff --git a/front/src/logic/nostril.ts b/front/src/logic/nostril.ts new file mode 100644 index 0000000..4e5549d --- /dev/null +++ b/front/src/logic/nostril.ts @@ -0,0 +1,36 @@ +import type { Event } from "@/types/nostr"; +import type { FC, FlatFeed, Poast } from "@/types/trill"; +import { engagementBunt, openLock } from "./bunts"; +export function eventsToFc(relayData: Record<string, Event[]>): FC { + const start = null; + const end = null; + const feed = Object.values(relayData).reduce((acc: FlatFeed, events) => { + const poasts = events.map(eventToPoast); + for (const p of poasts) { + if (p) acc[p.id] = p; + } + return acc; + }, {}); + return { feed, start, end }; +} +export function eventToPoast(event: Event): Poast | null { + if (event.kind !== 1) return null; + const contents = [{ paragraph: [{ text: event.content }] }]; + const ts = event.created_at * 1000; + const id = `${ts}`; + const poast: Poast = { + id, + host: event.pubkey, + author: event.pubkey, + contents, + thread: id, + parent: null, + read: openLock, + write: openLock, + tags: [], + time: ts, + engagement: engagementBunt, + children: [], + }; + return poast; +} diff --git a/front/src/logic/requests/nostril.ts b/front/src/logic/requests/nostril.ts new file mode 100644 index 0000000..6f0edcf --- /dev/null +++ b/front/src/logic/requests/nostril.ts @@ -0,0 +1,139 @@ +import type Urbit from "urbit-api"; +import type { Cursor, PostID, SentPoast } from "@/types/trill"; +import type { Ship } from "@/types/urbit"; +import { FeedPostCount } from "../constants"; +import type { UserProfile } from "@/types/nostril"; + +// Subscribe +type Handler = (date: any) => void; +export default class IO { + airlock; + constructor(airlock: Urbit) { + this.airlock = airlock; + } + private async poke(json: any) { + return this.airlock.poke({ app: "nostril", mark: "json", json }); + } + private async scry(path: string) { + return this.airlock.scry({ app: "nostril", path }); + } + private async sub(path: string, handler: Handler) { + const err = (err: any, _id: string) => + console.log(err, "error on nostril subscription"); + const quit = (data: any) => + console.log(data, "nostril subscription kicked"); + const res = await this.airlock.subscribe({ + app: "nostril", + path, + event: handler, + err, + quit, + }); + console.log(res, "subscribed to nostril agent"); + } + async unsub(sub: number) { + return await this.airlock.unsubscribe(sub); + } + // subs + async subscribeStore(handler: Handler) { + const res = await this.sub("/ui", handler); + return res; + } + // scries + + async scryFeed(start: Cursor, end: Cursor, desc = true) { + const order = desc ? 1 : 0; + const term = "feed"; + + const path = `/j/feed/${term}/${start}/${end}/${FeedPostCount}/${order}`; + return await this.scry(path); + } + async scryPost( + host: Ship, + id: PostID, + start: Cursor, + end: Cursor, + desc = true, + ) { + const order = desc ? 1 : 0; + + const path = `/j/post/${host}/${id}/${start}/${end}/${FeedPostCount}/${order}`; + return await this.scry(path); + } + // pokes + + async pokeAlive() { + return await this.poke({ alive: true }); + } + async addPost(pubkey: string, content: string) { + const json = { add: { pubkey, content } }; + return this.poke({ post: json }); + } + // async addPost(post: SentPoast, gossip: boolean) { + // const json = { + // "new-post": { + // "sent-post": post, + // gossip, + // }, + // }; + // return this.poke(json); + // } + + async deletePost(id: string) { + const host = `~${this.airlock.ship}`; + const json = { + "del-post": { + ship: host, + id: id, + }, + }; + return this.poke(json); + } + + async addReact(ship: Ship, id: PostID, reaction: string) { + const json = { + "new-react": { + react: reaction, + pid: { + id: id, + ship: ship, + }, + }, + }; + + return this.poke(json); + } + + // follows + async follow(ship: Ship) { + const json = { add: ship }; + return this.poke({ fols: json }); + } + + async unfollow(ship: Ship) { + const json = { del: ship }; + return await this.poke({ fols: json }); + } + // profiles + async createProfile(pubkey: string, profile: UserProfile) { + const json = { add: { pubkey, profile } }; + return await this.poke({ prof: json }); + } + async createKey() { + const json = { add: null }; + return await this.poke({ keys: json }); + } + async removeKey(pubkey: string) { + const json = { del: pubkey }; + return await this.poke({ keys: json }); + } + // relaying + async relayPost(host: string, id: string, relays: string[]) { + const json = { send: { host, id, relays } }; + return await this.poke({ rela: json }); + } +} + +// notifications + +// mark as read diff --git a/front/src/logic/utils.ts b/front/src/logic/utils.ts new file mode 100644 index 0000000..dbd246e --- /dev/null +++ b/front/src/logic/utils.ts @@ -0,0 +1,459 @@ +import type { + Content, + Notification, + ID, + ExternalContent, + Poast, + Reference, + Inline, + PID, + SortugRef, +} from "@/types/trill"; +import type { Ship } from "@/types/urbit"; +import anyAscii from "any-ascii"; +import type { ReactGrouping, SPID } from "@/types/ui"; +import { openLock } from "./bunts"; +import { isValidPatp, patp2dec } from "urbit-ob"; +import { REF_REGEX } from "./constants"; + +export function parseSortugLink(link: string): SortugRef { + const s = link.replace("urbit://", "").split("/"); + const [type, ship, ...pat] = s; + const path = `/${pat.join("/")}`; + return { type, ship, path }; +} +export function sortugRefTolink(r: SortugRef): string { + return `urbit://${r.type}/${r.ship}${r.path}`; +} +// TODO + +export function createReference(ship: Ship, id: ID) { + return { + reference: { + feed: { id: id, ship: ship }, + }, + }; +} + +export function addScheme(url: string) { + if (url.includes("localhost")) { + return `http://${url.replace("http://", "")}`; + } else { + return `https://${url.replace("http://", "")}`; + } +} + +export function easyCode(code: string) { + const string = code.replace(/-/g, ""); + const matches = string.match(/.{1,6}/g); + if (matches) return matches.join("-"); +} + +export function tilde(patp: Ship) { + if (patp[0] == "~") { + return patp; + } else { + return "~" + patp; + } +} + +export function color_to_hex(color: string) { + let hex = "#" + color.replace(".", "").replace("0x", "").toUpperCase(); + if (hex == "#0") { + hex = "#000000"; + } + return hex; +} + +export function date_diff(date: number | Date, type: "short" | "long") { + const now = new Date().getTime(); + const diff = now - new Date(date).getTime(); + if (type == "short") { + return to_string(diff / 1000); + } else { + return to_string_long(diff / 1000); + } +} + +function to_string(s: number) { + if (s < 60) { + return "now"; + } else if (s < 3600) { + return `${Math.ceil(s / 60)}m`; + } else if (s < 86400) { + return `${Math.ceil(s / 60 / 60)}h`; + } else if (s < 2678400) { + return `${Math.ceil(s / 60 / 60 / 24)}d`; + } else if (s < 32140800) { + return `${Math.ceil(s / 60 / 60 / 24 / 30)}mo`; + } else { + return `${Math.ceil(s / 60 / 60 / 24 / 30 / 12)}y`; + } +} + +function to_string_long(s: number) { + if (s < 60) { + return "right now"; + } else if (s < 3600) { + return `${Math.ceil(s / 60)} minutes ago`; + } else if (s < 86400) { + return `${Math.ceil(s / 60 / 60)} hours ago`; + } else if (s < 2678400) { + return `${Math.ceil(s / 60 / 60 / 24)} days ago`; + } else if (s < 32140800) { + return `${Math.ceil(s / 60 / 60 / 24 / 30)} months ago`; + } else { + return `${Math.ceil(s / 60 / 60 / 24 / 30 / 12)} years ago`; + } +} + +export function regexes() { + const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i); + const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg)$/i); + const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i); + return { img: IMAGE_REGEX, aud: AUDIO_REGEX, vid: VIDEO_REGEX }; +} + +export function stringToSymbol(str: string) { + const ascii = anyAscii(str); + let result = ""; + for (let i = 0; i < ascii.length; i++) { + const n = ascii.charCodeAt(i); + if ((n >= 97 && n <= 122) || (n >= 48 && n <= 57)) { + result += ascii[i]; + } else if (n >= 65 && n <= 90) { + result += String.fromCharCode(n + 32); + } else { + result += "-"; + } + } + result = result.replace(/^[\-\d]+|\-+/g, "-"); + result = result.replace(/^\-+|\-+$/g, ""); + return result; +} +export function buildDM(author: Ship, recipient: Ship, contents: Content[]) { + const node: any = {}; + const point = patp2dec(recipient); + const index = `/${point}/${makeIndex()}`; + node[index] = { + children: null, + post: { + author: author, + contents: contents, + hash: null, + index: index, + signatures: [], + "time-sent": Date.now(), + }, + }; + return { + app: "dm-hook", + mark: "graph-update-3", + json: { + "add-nodes": { + resource: { name: "dm-inbox", ship: author }, + nodes: node, + }, + }, + }; +} + +export function makeIndex(): string { + const DA_UNIX_EPOCH = BigInt("170141184475152167957503069145530368000"); + const DA_SECOND = BigInt("18446744073709551616"); + const timeSinceEpoch = (BigInt(Date.now()) * DA_SECOND) / BigInt(1000); + return (DA_UNIX_EPOCH + timeSinceEpoch).toString(); +} +export function makeDottedIndex() { + const DA_UNIX_EPOCH = BigInt("170141184475152167957503069145530368000"); + const DA_SECOND = BigInt("18446744073709551616"); + const timeSinceEpoch = (BigInt(Date.now()) * DA_SECOND) / BigInt(1000); + const index = (DA_UNIX_EPOCH + timeSinceEpoch).toString(); + return index.replace(/\B(?=(\d{3})+(?!\d))/g, "."); +} + +export function repostData(p: Poast): PID | null { + if ( + p.contents.length === 1 && + "ref" in p.contents[0] && + p.contents[0].ref.type === "trill" + ) + return { + id: p.contents[0].ref.path.slice(1), + ship: p.contents[0].ref.ship, + }; + else return null; +} + +export function getNotificationTime(n: Notification): number { + if ("follow" in n) { + return n.follow.time; + } else if ("unfollow" in n) { + return n.unfollow.time; + } else if ("mention" in n) { + return n.mention.time; + } else if ("react" in n) { + return n.react.time; + } else if ("reply" in n) { + return n.reply.time; + } else if ("quote" in n) { + return n.quote.time; + } else if ("share" in n) { + return n.share.time; + } else { + return Date.now(); + } +} +export function abbreviateChat(s: string): string { + const plist = s.trim().split(" "); + if (isValidPatp(plist[0]) && plist.length > 1) { + return `${plist[0]} & ${plist.length - 1}+`; + } else if (s.length < 25) return s; + else return `${s.substring(0, 25)}...`; +} + +export function timestring(n: number): string { + const nn = new Date(n); + return nn.toTimeString().slice(0, 5); +} +export function wait(ms: number) { + return new Promise((resolve, _reject) => { + setTimeout(resolve, ms); + }); +} + +export function quoteToReference(d: SPID): Reference | ExternalContent { + if (d.service === "twatter") + return { + json: { + origin: "twatter", + content: JSON.stringify(d.post), + }, + }; + else + return { + ref: { + type: "trill", + ship: d.post.host, + path: `/${d.post.id}`, + }, + }; +} + +export function trillPermalink(t: Poast) { + return `urbit://trill/${t.host}/${t.id}`; +} +export function isFeedRef(c: Content): boolean { + return "ref" in c && (c as Reference).ref.type === "trill"; +} + +export function checkTilde(s: string) { + if (s[0] === "~") return s; + else return "~" + s; +} + +export function addDots(s: string, num: number): string { + const reversed = s.split("").reverse().join(""); + const reg = new RegExp(`.{${num}}`, "g"); + const withCommas = reversed.replace(reg, "$&."); + return withCommas.split("").reverse().join("").slice(1); +} +export function addDots5(s: string): string { + const reversed = s.split("").reverse().join(""); + const withCommas = reversed.replace(/.{5}/g, "$&."); + return withCommas.split("").reverse().join(""); +} +// TODO +export function getTrillText(c: Content): string { + if (!c) return ""; + const reducePara = (acc: string, item: Inline) => { + let t = ""; + if ("text" in item) t = item.text + " "; + if ("italic" in item) t = item.italic + " "; + if ("bold" in item) t = item.bold + " "; + if ("strike" in item) t = item.strike + " "; + if ("ship" in item) t = item.ship + " "; + if ("codespan" in item) t = item.codespan + " "; + if ("link" in item) t = item.link.href + " "; + if ("break" in item) t = "\n"; + return acc + t; + }; + return c.reduce((acc, item) => { + if ("paragraph" in item) { + const text = item.paragraph.reduce(reducePara, ""); + return acc + text + "\n"; + } else return acc; + }, ""); +} +export function isTwatterLink(s: string) { + const sp = s + .replace("https://", "") + .split("/") + .filter((s) => s); + return sp.length === 4 && sp[0] === "twitter.com" && sp[2] === "status"; +} +export const isSortugLink = (s: string) => !!s.match(REF_REGEX); +export function parseOutSortugLinks(s: string): [SortugRef[], string] { + const matches = s.match(REF_REGEX); + let refs = []; + let rest = s; + for (let m of matches || []) { + rest = rest.replace(m, ""); + refs.push(parseSortugLink(m)); + } + return [refs, rest]; +} + +export function isTrillLink(s: string): boolean { + if (!isSortugLink(s)) return false; + const r = parseSortugLink(s); + if (r.type !== "trill") return false; + return isValidPatp(r.ship) && !isNaN(Number(r.path.slice(1))); +} + +export function auraToHex(s: string): string { + if (s.startsWith("0x")) { + let numbers = s.replace("0x", "").replace(".", ""); + while (numbers.length < 6) { + numbers = "0" + numbers; + } + return "#" + numbers; + } else if (s.startsWith("#")) return s; + else { + // console.log(s, "weird hex"); + return "black"; + } +} + +export function buildPost( + author: Ship, + id: string, + time: number, + s: string, + content: string, +): Poast { + return { + host: author, + author: author, + thread: null, + parent: null, + contents: [{ paragraph: [{ text: s }] }], + read: openLock, + write: openLock, + tags: [], + id, + time, + children: [], + engagement: { reacts: {}, quoted: [], shared: [] }, + json: { origin: "rumors", content }, + }; +} + +// default cursors +export function makeNewestIndex() { + const DA_UNIX_EPOCH = BigInt("170141184475152167957503069145530368000"); + const DA_SECOND = BigInt("18446744073709551616"); + const timeSinceEpoch = (BigInt(Date.now()) * DA_SECOND) / BigInt(1000); + return (DA_UNIX_EPOCH + timeSinceEpoch).toString(); +} +export const startCursor = makeNewestIndex(); +export const endCursor = "0"; + +export function displayCount(c: number): string { + if (c <= 0) return ""; + if (c < 1_000) return `${c}`; + if (c >= 1_000 && c < 1_000_000) return `${Math.round(c / 1_00) / 10}K`; + if (c >= 1_000_000) return `${Math.round(c / 100_000) / 10}M`; + else return ""; +} +export function isWhiteish(hex: string): boolean { + if (hex.indexOf("#") === 0) hex = hex.slice(1); + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + return r > 200 && g > 200 && b > 200; +} + +export function localISOString(date: Date) { + const offset = new Date().getTimezoneOffset(); + const localts = date.getTime() - offset * 60_000; + return new Date(localts).toISOString().slice(0, 16); +} + +export function goback() { + window.history.back(); +} + +export function groupReacts(reacts: Record<Ship, string>): ReactGrouping { + const byReact = Object.entries(reacts).reduce( + (acc: Record<string, Ship[]>, item) => { + const shipList = acc[item[1]]; + if (!shipList) acc[item[1]] = [item[0]]; + else acc[item[1]] = [...shipList, item[0]]; + return acc; + }, + {}, + ); + return Object.entries(byReact) + .reduce((acc: ReactGrouping, item) => { + const pair = { react: item[0], ships: item[1] }; + return [...acc, pair]; + }, []) + .sort((a, b) => b.ships.length - a.ships.length); +} + +export function reverseRecord( + a: Record<string, string>, +): Record<string, string> { + return Object.entries(a).reduce((acc: Record<string, string>, [k, v]) => { + acc[v] = k; + return acc; + }, {}); +} + +export function getColorHex(color: string): string { + if (color.startsWith("0x")) + return `#${padString(stripFuckingDots(color), 6)}`; + else if (color.startsWith("#") && color.length === 7) return color; + else if (color.length === 6) return `#${color}`; + else { + console.log(color, "something weird with this color"); + return "#FFFFFF"; + } +} + +export function stripFuckingDots(hex: string) { + return hex.replace("0x", "").replaceAll(".", ""); +} +export function padString(s: string, size: number) { + if (s.length >= size) return s; + else return padString(`0${s}`, size); +} +export function isDark(hexColor: string): boolean { + const r = parseInt(hexColor.substring(1, 2), 16); + const g = parseInt(hexColor.substring(3, 5), 16); + const b = parseInt(hexColor.substring(5, 7), 16); + + const sr = r / 255; + const sg = g / 255; + const sb = b / 255; + const rSrgb = + sr <= 0.03928 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4); + const gSrgb = + sg <= 0.03928 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4); + const bSrgb = + sb <= 0.03928 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4); + + // Calculate luminance + const luminance = 0.2126 * rSrgb + 0.7152 * gSrgb + 0.0722 * bSrgb; + return luminance < 0.12; +} + +export function checkIfClickedOutside( + e: React.MouseEvent, + el: HTMLElement, + close: any, +) { + e.stopPropagation(); + if (el.contains(e.currentTarget)) close(); +} diff --git a/front/src/main.tsx b/front/src/main.tsx new file mode 100644 index 0000000..5d4a2be --- /dev/null +++ b/front/src/main.tsx @@ -0,0 +1,9 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; + +createRoot(document.getElementById("root")!).render( + <StrictMode> + <App /> + </StrictMode>, +); diff --git a/front/src/pages/Feed.tsx b/front/src/pages/Feed.tsx new file mode 100644 index 0000000..e29033e --- /dev/null +++ b/front/src/pages/Feed.tsx @@ -0,0 +1,104 @@ +// import spinner from "@/assets/icons/spinner.svg"; +import "@/styles/trill.css"; +import UserFeed from "./User"; +import PostList from "@/components/feed/PostList"; +import useLocalState from "@/state/state"; +import { useParams, useLocation } from "wouter"; +import spinner from "@/assets/triangles.svg"; +import { useState } from "react"; +import Composer from "@/components/feed/Composer"; +// import UserFeed from "./User"; +import { P404 } from "@/Router"; +import { useQuery } from "@tanstack/react-query"; +import { isValidPatp } from "urbit-ob"; +import { eventsToFc } from "@/logic/nostril"; + +type FeedType = "global" | "following" | "nostr"; +function Loader() { + // const { api } = useLocalState(); + const params = useParams(); + console.log({ params }); + // const [loc, navigate] = useLocation(); + // console.log({ loc }); + // const our = api!.airlock.ship; + if (params.taip === "global") return <FeedPage t={"global"} />; + if (params.taip === "nostr") return <FeedPage t={"nostr"} />; + // else if (param === FeedType.Rumors) return <Rumors />; + // else if (param === FeedType.Home) return <UserFeed p={our} />; + else if (isValidPatp(params.taip!)) return <UserFeed p={params.taip!} />; + else return <P404 />; +} +function FeedPage({ t }: { t: FeedType }) { + const [active, setActive] = useState<FeedType>(t); + return ( + <main> + <div id="top-tabs"> + <div + className={active === "global" ? "active" : ""} + onClick={() => setActive("global")} + > + Global + </div> + <div + className={active === "following" ? "active" : ""} + onClick={() => setActive("following")} + > + Following + </div> + <div + className={active === "nostr" ? "active" : ""} + onClick={() => setActive("nostr")} + > + Nostr + </div> + </div> + <div id="feed-proper"> + <Composer /> + {active === "global" ? ( + <Global /> + ) : active === "following" ? ( + <Global /> + ) : active === "nostr" ? ( + <Nostr /> + ) : null} + </div> + </main> + ); +} +// {active === "global" ? ( +// <Global /> +// ) : active === "following" ? ( +// <Global /> +// ) : ( +// <Global /> +// )} + +function Global() { + // const { api } = useLocalState(); + // const { isPending, data, refetch } = useQuery({ + // queryKey: ["globalFeed"], + // queryFn: () => { + // return api!.scryFeed(null, null); + // }, + // }); + // console.log(data, "scry feed data"); + // if (isPending) return <img className="x-center" src={spinner} />; + // else if ("bucun" in data) return <p>Error</p>; + // else return <Inner data={data} refetch={refetch} />; + return <p>Error</p>; +} +function Nostr() { + const { relays } = useLocalState(); + const feed = eventsToFc(relays); + console.log({ feed }); + const refetch = () => feed; + return <PostList data={feed} refetch={refetch} />; +} + +export default Loader; +// TODO +type MixFeed = any; + +function Inner({ data, refetch }: { data: MixFeed; refetch: Function }) { + return <PostList data={data.mix.fc} refetch={refetch} />; +} diff --git a/front/src/pages/Settings.tsx b/front/src/pages/Settings.tsx new file mode 100644 index 0000000..e0f1da9 --- /dev/null +++ b/front/src/pages/Settings.tsx @@ -0,0 +1,92 @@ +import useLocalState from "@/state/state"; +import type { UserProfile } from "@/types/nostril"; +import { useState } from "react"; + +function Settings() { + const { UISettings, keys, profiles, relays, api } = useLocalState(); + const [newRelay, setNewRelay] = useState(""); + async function saveSetting( + bucket: string, + key: string, + value: string | boolean | number | string[], + ) { + const json = { + "put-entry": { + desk: "trill", + "bucket-key": bucket, + "entry-key": key, + value, + }, + }; + // const res = await poke("settings", "settings-event", json); + // if (res) refetchSettings(); + } + async function removeRelay(url: string) { + console.log({ url }); + } + async function addNewRelay() { + // + // await addnr(newRelay); + } + async function removeProfile(pubkey: string) { + api!.removeKey(pubkey); + } + async function createProfile() { + // + api!.createKey(); + } + + return ( + <div id="settings"> + <h1>Settings</h1> + <div className="setting"> + <label>Pubkeys</label> + {keys.map((k) => { + const profile = profiles.get(k); + const profileDiv = !profile ? ( + <div className="profile"> + <div>Pubkey: {k}</div> + <p>No profile set</p>) + </div> + ) : ( + <div className="profile"> + {profile.picture && <img src={profile.picture} />} + <div>Name: {profile.name}</div> + <div>Pubkey: {k}</div> + <div>About: {profile.about}</div> + <button onClick={() => removeProfile(k)}>x</button> + </div> + ); + return ( + <div className="options flex" key={k}> + {profileDiv} + </div> + ); + })} + <div className="options flex"> + <button onClick={createProfile}>Create New</button> + </div> + </div> + <div className="setting"> + <label>Nostr Relays</label> + {Object.keys(relays).map((r) => ( + // TODO: add connect button to connect and disc to relay one by one + <div className="options flex" key={r}> + <div>{r}</div> + <button onClick={() => removeRelay(r)}>x</button> + </div> + ))} + <div className="options flex"> + <label>Add new</label> + <input + type="text" + value={newRelay} + onChange={(e) => setNewRelay(e.target.value)} + /> + <button onClick={addNewRelay}>Add</button> + </div> + </div> + </div> + ); +} +export default Settings; diff --git a/front/src/pages/User.tsx b/front/src/pages/User.tsx new file mode 100644 index 0000000..fc727e4 --- /dev/null +++ b/front/src/pages/User.tsx @@ -0,0 +1,18 @@ +// import spinner from "@/assets/icons/spinner.svg"; +import PostList from "@/components/feed/PostList"; +import useLocalState from "@/state/state"; +import type { Ship } from "@/types/urbit"; + +function UserFeed({ p }: { p: Ship }) { + const { api, following } = useLocalState(); + const feed = following.get(api!.airlock.our!); + const refetch = () => feed; + if (p === api!.airlock.our) + return ( + <div id="feed-proper"> + <PostList data={feed!} refetch={refetch} /> + </div> + ); +} + +export default UserFeed; diff --git a/front/src/state/state.ts b/front/src/state/state.ts new file mode 100644 index 0000000..28f3fb2 --- /dev/null +++ b/front/src/state/state.ts @@ -0,0 +1,64 @@ +import type { JSX } from "react"; +import { start } from "@/logic/api"; +import IO from "@/logic/requests/nostril"; +import type { ComposerData } from "@/types/ui"; +import { create } from "zustand"; +import type { UserProfile } from "@/types/nostril"; +import type { Event } from "@/types/nostr"; +import type { FC } from "@/types/trill"; +// TODO handle airlock connection issues +// the SSE pipeline has a "status-update" event FWIW +// type AirlockState = "connecting" | "connected" | "failed"; +export type LocalState = { + isNew: boolean; + api: IO | null; + init: () => Promise<void>; + UISettings: Record<string, any>; + modal: JSX.Element | null; + setModal: (modal: JSX.Element | null) => void; + composerData: ComposerData | null; + setComposerData: (c: ComposerData | null) => void; + keys: string[]; + relays: Record<string, Event[]>; + profiles: Map<string, UserProfile>; // pubkey key + following: Map<string, FC>; + followers: string[]; +}; + +const creator = create<LocalState>(); +const useLocalState = creator((set, _get) => ({ + isNew: false, + api: null, + init: async () => { + const airlock = await start(); + const api = new IO(airlock); + console.log({ api }); + await api.subscribeStore((data) => { + console.log("store sub", data); + const { feed, following, relays, profiles, keys } = data; + + const flwing = new Map(Object.entries(following as Record<string, FC>)); + flwing.set(api!.airlock.our!, feed); + set({ + relays, + profiles: new Map(Object.entries(profiles)), + following: flwing, + keys, + }); + }); + set({ api }); + }, + keys: [], + profiles: new Map(), + relays: {}, + following: new Map(), + followers: [], + UISettings: {}, + modal: null, + setModal: (modal) => set({ modal }), + // composer data + composerData: null, + setComposerData: (composerData) => set({ composerData }), +})); + +export default useLocalState; diff --git a/front/src/styles/ThemeProvider.tsx b/front/src/styles/ThemeProvider.tsx new file mode 100644 index 0000000..2cc0ca6 --- /dev/null +++ b/front/src/styles/ThemeProvider.tsx @@ -0,0 +1,302 @@ +import React, { + createContext, + useContext, + useEffect, + useState, + type ReactNode, +} from "react"; + +export type ThemeName = + | "light" + | "dark" + | "sepia" + | "noir" + | "ocean" + | "forest" + | "gruvbox"; + +export interface ThemeColors { + primary: string; + primaryHover: string; + secondary: string; + background: string; + surface: string; + surfaceHover: string; + text: string; + textSecondary: string; + textMuted: string; + border: string; + borderLight: string; + success: string; + warning: string; + error: string; + info: string; + link: string; + linkHover: string; + shadow: string; + overlay: string; +} + +export interface Theme { + name: ThemeName; + colors: ThemeColors; +} + +const themes: Record<ThemeName, Theme> = { + light: { + name: "light", + colors: { + primary: "#543fd7", + primaryHover: "#4532b8", + secondary: "#f39c12", + background: "#ffffff", + surface: "#f8f9fa", + surfaceHover: "#e9ecef", + text: "#212529", + textSecondary: "#495057", + textMuted: "#6c757d", + border: "#dee2e6", + borderLight: "#e9ecef", + success: "#28a745", + warning: "#ffc107", + error: "#dc3545", + info: "#17a2b8", + link: "#543fd7", + linkHover: "#4532b8", + shadow: "rgba(0, 0, 0, 0.1)", + overlay: "rgba(0, 0, 0, 0.5)", + }, + }, + dark: { + name: "dark", + colors: { + primary: "#7c6ef7", + primaryHover: "#9085f9", + secondary: "#f39c12", + background: "#0d1117", + surface: "#161b22", + surfaceHover: "#21262d", + text: "#c9d1d9", + textSecondary: "#8b949e", + textMuted: "#6e7681", + border: "#30363d", + borderLight: "#21262d", + success: "#3fb950", + warning: "#d29922", + error: "#f85149", + info: "#58a6ff", + link: "#58a6ff", + linkHover: "#79b8ff", + shadow: "rgba(0, 0, 0, 0.3)", + overlay: "rgba(0, 0, 0, 0.7)", + }, + }, + sepia: { + name: "sepia", + colors: { + primary: "#8b4513", + primaryHover: "#6b3410", + secondary: "#d2691e", + background: "#f4e8d0", + surface: "#ede0c8", + surfaceHover: "#e6d9c0", + text: "#3e2723", + textSecondary: "#5d4037", + textMuted: "#6d4c41", + border: "#d7ccc8", + borderLight: "#e0d5d0", + success: "#689f38", + warning: "#ff9800", + error: "#d32f2f", + info: "#0288d1", + link: "#8b4513", + linkHover: "#6b3410", + shadow: "rgba(62, 39, 35, 0.1)", + overlay: "rgba(62, 39, 35, 0.5)", + }, + }, + noir: { + name: "noir", + colors: { + primary: "#ffffff", + primaryHover: "#e0e0e0", + secondary: "#808080", + background: "#000000", + surface: "#0a0a0a", + surfaceHover: "#1a1a1a", + text: "#ffffff", + textSecondary: "#b0b0b0", + textMuted: "#808080", + border: "#333333", + borderLight: "#1a1a1a", + success: "#4caf50", + warning: "#ff9800", + error: "#f44336", + info: "#2196f3", + link: "#b0b0b0", + linkHover: "#ffffff", + shadow: "rgba(255, 255, 255, 0.1)", + overlay: "rgba(0, 0, 0, 0.9)", + }, + }, + ocean: { + name: "ocean", + colors: { + primary: "#006994", + primaryHover: "#005577", + secondary: "#00acc1", + background: "#e1f5fe", + surface: "#b3e5fc", + surfaceHover: "#81d4fa", + text: "#01579b", + textSecondary: "#0277bd", + textMuted: "#4fc3f7", + border: "#81d4fa", + borderLight: "#b3e5fc", + success: "#00c853", + warning: "#ffab00", + error: "#d50000", + info: "#00b0ff", + link: "#0277bd", + linkHover: "#01579b", + shadow: "rgba(1, 87, 155, 0.1)", + overlay: "rgba(1, 87, 155, 0.5)", + }, + }, + forest: { + name: "forest", + colors: { + primary: "#2e7d32", + primaryHover: "#1b5e20", + secondary: "#689f38", + background: "#f1f8e9", + surface: "#dcedc8", + surfaceHover: "#c5e1a5", + text: "#1b5e20", + textSecondary: "#33691e", + textMuted: "#558b2f", + border: "#aed581", + borderLight: "#c5e1a5", + success: "#4caf50", + warning: "#ff9800", + error: "#f44336", + info: "#03a9f4", + link: "#388e3c", + linkHover: "#2e7d32", + shadow: "rgba(27, 94, 32, 0.1)", + overlay: "rgba(27, 94, 32, 0.5)", + }, + }, + gruvbox: { + name: "gruvbox", + colors: { + primary: "#fe8019", + primaryHover: "#d65d0e", + secondary: "#fabd2f", + background: "#282828", + surface: "#3c3836", + surfaceHover: "#504945", + text: "#ebdbb2", + textSecondary: "#d5c4a1", + textMuted: "#bdae93", + border: "#665c54", + borderLight: "#504945", + success: "#b8bb26", + warning: "#fabd2f", + error: "#fb4934", + info: "#83a598", + link: "#8ec07c", + linkHover: "#b8bb26", + shadow: "rgba(0, 0, 0, 0.3)", + overlay: "rgba(40, 40, 40, 0.8)", + }, + }, +}; + +interface ThemeContextType { + theme: Theme; + themeName: ThemeName; + setTheme: (name: ThemeName) => void; + availableThemes: ThemeName[]; +} + +const ThemeContext = createContext<ThemeContextType | undefined>(undefined); + +interface ThemeProviderProps { + children: ReactNode; + defaultTheme?: ThemeName; +} + +export const ThemeProvider: React.FC<ThemeProviderProps> = ({ + children, + defaultTheme = "light", +}) => { + const [themeName, setThemeName] = useState<ThemeName>(() => { + const savedTheme = localStorage.getItem("theme") as ThemeName; + if (savedTheme && themes[savedTheme]) { + return savedTheme; + } + + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + return "dark"; + } + + return defaultTheme; + }); + + const theme = themes[themeName]; + + useEffect(() => { + const root = document.documentElement; + + root.setAttribute("data-theme", themeName); + + Object.entries(theme.colors).forEach(([key, value]) => { + const cssVarName = `--color-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; + root.style.setProperty(cssVarName, value); + }); + + localStorage.setItem("theme", themeName); + }, [themeName, theme]); + + useEffect(() => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleChange = (e: MediaQueryListEvent) => { + const savedTheme = localStorage.getItem("theme"); + if (!savedTheme) { + setThemeName(e.matches ? "dark" : "light"); + } + }; + + mediaQuery.addEventListener("change", handleChange); + return () => mediaQuery.removeEventListener("change", handleChange); + }, []); + + const setTheme = (name: ThemeName) => { + if (themes[name]) { + setThemeName(name); + } + }; + + const value: ThemeContextType = { + theme, + themeName, + setTheme, + availableThemes: Object.keys(themes) as ThemeName[], + }; + + return ( + <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider> + ); +}; + +export const useTheme = (): ThemeContextType => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error("useTheme must be used within a ThemeProvider"); + } + return context; +}; diff --git a/front/src/styles/ThemeSwitcher.css b/front/src/styles/ThemeSwitcher.css new file mode 100644 index 0000000..518a00d --- /dev/null +++ b/front/src/styles/ThemeSwitcher.css @@ -0,0 +1,249 @@ +/* Theme Switcher Styles */ + +/* Compact variant */ +.theme-switcher-compact { + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm) var(--spacing-md); + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-full); + cursor: pointer; + transition: all var(--transition-fast); + font-size: var(--font-md); + color: var(--color-text); +} + +.theme-switcher-compact:hover { + background-color: var(--color-surface-hover); + border-color: var(--color-primary); + transform: scale(1.05); +} + +.theme-switcher-compact:active { + transform: scale(0.98); +} + +.theme-switcher-compact .theme-icon { + font-size: 1.2em; + display: flex; + align-items: center; +} + +.theme-switcher-compact .theme-label { + font-weight: var(--font-medium); +} + +/* Buttons variant */ +.theme-switcher-buttons { + display: flex; + align-items: center; + gap: var(--spacing-md); +} + +.theme-switcher-buttons .theme-label { + color: var(--color-text-secondary); + font-weight: var(--font-medium); +} + +.theme-buttons-group { + display: flex; + gap: var(--spacing-xs); + background-color: var(--color-surface); + padding: var(--spacing-xs); + border-radius: var(--radius-lg); + border: 1px solid var(--color-border); +} + +.theme-button { + display: flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + background-color: transparent; + border: 1px solid transparent; + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-fast); + color: var(--color-text-secondary); + font-size: var(--font-sm); +} + +.theme-button:hover { + background-color: var(--color-surface-hover); + color: var(--color-text); +} + +.theme-button.active { + background-color: var(--color-primary); + color: white; + border-color: var(--color-primary); +} + +.theme-button .theme-icon { + font-size: 1.1em; +} + +.theme-button .theme-name { + display: none; +} + +@media (min-width: 768px) { + .theme-button .theme-name { + display: inline; + } +} + +/* Dropdown variant */ +.theme-switcher-dropdown { + position: relative; + display: inline-block; +} + +.theme-dropdown-toggle { + display: flex; + align-items: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm) var(--spacing-md); + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-fast); + color: var(--color-text); + font-size: var(--font-md); +} + +.theme-dropdown-toggle:hover { + background-color: var(--color-surface-hover); + border-color: var(--color-primary); +} + +.theme-dropdown-toggle .theme-icon { + font-size: 1.2em; +} + +.theme-dropdown-toggle .theme-label { + font-weight: var(--font-medium); +} + +.theme-dropdown-toggle .dropdown-arrow { + font-size: 0.7em; + margin-left: var(--spacing-xs); + transition: transform var(--transition-fast); + color: var(--color-text-muted); +} + +.theme-dropdown-toggle[aria-expanded="true"] .dropdown-arrow { + transform: rotate(180deg); +} + +.theme-dropdown-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: var(--z-dropdown); + background-color: transparent; +} + +.theme-dropdown-menu { + position: absolute; + top: calc(100% + var(--spacing-xs)); + right: 0; + min-width: 180px; + background-color: var(--color-background); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + box-shadow: 0 4px 12px var(--color-shadow); + z-index: calc(var(--z-dropdown) + 1); + padding: var(--spacing-xs); + animation: dropdownSlide 0.2s ease-out; +} + +@keyframes dropdownSlide { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.theme-dropdown-item { + display: flex; + align-items: center; + gap: var(--spacing-sm); + width: 100%; + padding: var(--spacing-sm) var(--spacing-md); + background-color: transparent; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-fast); + color: var(--color-text); + font-size: var(--font-md); + text-align: left; +} + +.theme-dropdown-item:hover { + background-color: var(--color-surface); +} + +.theme-dropdown-item.active { + background-color: var(--color-surface); + color: var(--color-primary); + font-weight: var(--font-medium); +} + +.theme-dropdown-item .theme-icon { + font-size: 1.2em; + width: 1.5em; + text-align: center; +} + +.theme-dropdown-item .theme-name { + flex: 1; +} + +.theme-dropdown-item .checkmark { + color: var(--color-success); + font-weight: var(--font-bold); +} + +/* Accessibility */ +.theme-switcher-compact:focus, +.theme-button:focus, +.theme-dropdown-toggle:focus, +.theme-dropdown-item:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +/* Dark theme adjustments */ +[data-theme="dark"] .theme-dropdown-menu { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + .theme-switcher-compact, + .theme-button, + .theme-dropdown-toggle, + .theme-dropdown-item, + .dropdown-arrow { + transition: none; + } + + .theme-dropdown-menu { + animation: none; + } + + .theme-switcher-compact:hover { + transform: none; + } +}
\ No newline at end of file diff --git a/front/src/styles/ThemeSwitcher.tsx b/front/src/styles/ThemeSwitcher.tsx new file mode 100644 index 0000000..425bed9 --- /dev/null +++ b/front/src/styles/ThemeSwitcher.tsx @@ -0,0 +1,131 @@ +import React, { useState } from "react"; +import { useTheme, type ThemeName } from "../styles/ThemeProvider"; +import "./ThemeSwitcher.css"; + +interface ThemeSwitcherProps { + variant?: "dropdown" | "buttons" | "compact"; + showLabel?: boolean; +} + +const themeIcons: Record<ThemeName, string> = { + light: "☀️", + dark: "🌙", + sepia: "📜", + noir: "⚫", + ocean: "🌊", + forest: "🌲", + gruvbox: "🍂", +}; + +const themeLabels: Record<ThemeName, string> = { + light: "Light", + dark: "Dark", + sepia: "Sepia", + noir: "Noir", + ocean: "Ocean", + forest: "Forest", + gruvbox: "Gruvbox", +}; + +export const ThemeSwitcher: React.FC<ThemeSwitcherProps> = ({ + variant = "dropdown", + showLabel = true, +}) => { + const { themeName, setTheme, availableThemes } = useTheme(); + const [isOpen, setIsOpen] = useState(false); + + const handleThemeChange = (theme: ThemeName) => { + setTheme(theme); + setIsOpen(false); + }; + + const cycleTheme = () => { + const currentIndex = availableThemes.indexOf(themeName); + const nextIndex = (currentIndex + 1) % availableThemes.length; + setTheme(availableThemes[nextIndex]); + }; + + if (variant === "compact") { + return ( + <button + className="theme-switcher-compact" + onClick={cycleTheme} + title={`Current theme: ${themeLabels[themeName]}. Click to switch.`} + aria-label="Switch theme" + > + <span className="theme-icon">{themeIcons[themeName]}</span> + {showLabel && ( + <span className="theme-label">{themeLabels[themeName]}</span> + )} + </button> + ); + } + + if (variant === "buttons") { + return ( + <div className="theme-switcher-buttons"> + {showLabel && <span className="theme-label">Theme:</span>} + <div className="theme-buttons-group"> + {availableThemes.map((theme) => ( + <button + key={theme} + className={`theme-button ${themeName === theme ? "active" : ""}`} + onClick={() => handleThemeChange(theme)} + title={themeLabels[theme]} + aria-label={`Switch to ${themeLabels[theme]} theme`} + aria-pressed={themeName === theme} + > + <span className="theme-icon">{themeIcons[theme]}</span> + {showLabel && ( + <span className="theme-name">{themeLabels[theme]}</span> + )} + </button> + ))} + </div> + </div> + ); + } + + // Default dropdown variant + return ( + <div className="theme-switcher-dropdown"> + <button + className="theme-dropdown-toggle" + onClick={() => setIsOpen(!isOpen)} + aria-haspopup="true" + aria-expanded={isOpen} + > + <span className="theme-icon">{themeIcons[themeName]}</span> + {showLabel && ( + <span className="theme-label">{themeLabels[themeName]}</span> + )} + <span className="dropdown-arrow">▼</span> + </button> + + {isOpen && ( + <> + <div + className="theme-dropdown-backdrop" + onClick={() => setIsOpen(false)} + aria-hidden="true" + /> + <div className="theme-dropdown-menu" role="menu"> + {availableThemes.map((theme) => ( + <button + key={theme} + className={`theme-dropdown-item ${themeName === theme ? "active" : ""}`} + onClick={() => handleThemeChange(theme)} + role="menuitem" + aria-selected={themeName === theme} + > + <span className="theme-icon">{themeIcons[theme]}</span> + <span className="theme-name">{themeLabels[theme]}</span> + {themeName === theme && <span className="checkmark">✓</span>} + </button> + ))} + </div> + </> + )} + </div> + ); +}; diff --git a/front/src/styles/styles.css b/front/src/styles/styles.css new file mode 100644 index 0000000..c2b05d6 --- /dev/null +++ b/front/src/styles/styles.css @@ -0,0 +1,438 @@ +/* assets */ +/* fonts */ +@font-face { + font-family: "Inter"; + src: url(/fonts/Inter/Inter-VariableFont_opsz,wght.ttf); + font-weight: 100 900; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Inter"; + src: url(/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf); + font-weight: 100 900; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "Source Code Pro"; + src: url(/fonts/Source_Code_Pro/SourceCodePro-VariableFont_wght.ttf); + font-weight: 100 900; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Source Code Pro"; + src: url(/fonts/Source_Code_Pro/SourceCodePro-Italic-VariableFont_wght.ttf); + font-weight: 100 900; + font-style: italic; + font-display: swap; +} + +/* tailwindy */ + +.grow { + flex-grow: 1; +} + +button { + cursor: pointer; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} + +t .red { + background-color: rgb(200, 0, 0, 0.9); +} + +.tc, +.ct { + text-align: center; +} + +.cb { + margin: auto; +} + +.xc { + position: absolute; + left: 50%; + transform: translateX(-50%); +} + +.hidden { + display: none; +} + +.x-center { + margin: auto; + text-align: center; + display: block; +} + +.flex { + display: flex; +} + +.f1 { + display: flex; + justify-content: space-between; + align-items: center; +} + +.flex-align { + display: flex; + gap: 1rem; + align-items: center; +} + +.noscroll { + overflow: hidden; +} + +.scroll-y { + overflow-y: scroll; +} + +.cp { + cursor: pointer; +} + +.m0 { + margin: 0; +} + +.mb { + margin: 0 0 1rem 0; +} + +.mt { + margin-top: 1rem; +} + +.mr { + margin-right: 0.5rem; +} + +.s-50 { + width: 50px; +} + +.s-100 { + width: 100px; +} + +.border { + border: 1px solid var(--text-color); +} + +/* styles */ + +/* common */ +html { + box-sizing: border-box; + color: var(--text-color); + background-color: var(--background-color); +} + +html, +body, +#root, +#mobile-ui { + height: 100%; + width: 100vw; + overflow: hidden; + /* no scrolling!!!*/ +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +body { + margin: 0; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: var(--color-background); + color: var(--color-text); + line-height: 1.6; + transition: background-color var(--transition-normal), color var(--transition-normal); +} + +/* Typography */ +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: var(--spacing-md); + font-weight: var(--font-semibold); + line-height: 1.2; + color: var(--color-text); +} + +#root { + margin: 1rem 2rem; + height: 100%; + overflow-y: auto; + font-family: "Inter"; + + + display: flex; + + & #left-menu { + margin-right: 1rem; + + #logo { + display: flex; + gap: 0.3rem; + + & img { + width: 48px; + height: 48px; + } + } + + & .opt { + cursor: pointer; + display: flex; + gap: 1rem; + margin: 1rem 0; + + & img { + width: 24px; + height: 24px; + } + } + } + + & main { + width: 726px; + margin: auto; + height: 100vh; + + & #top-tabs { + display: flex; + gap: 2rem; + justify-content: center; + + & div { + cursor: pointer; + } + + & .active { + font-weight: 700; + border-bottom: 3px solid var(--color-text); + } + } + + & #feed-proper { + margin-top: 1rem; + border: 1px solid grey; + border-radius: 0.75rem; + + & #composer { + padding: 10px; + display: flex; + gap: 0.5rem; + + & .sigil { + width: 48px; + height: 48px; + + & img { + width: inherit; + } + } + + & input { + background-color: transparent; + color: var(--color-text); + flex-grow: 1; + border: none; + outline: none; + } + } + } + + & .trill-post, + & .twatter-post { + border-top: 1px solid grey; + + & .left { + margin-right: 10px; + width: unset; + + & .sigil { + width: 48px; + height: 48px; + } + } + + & header { + align-items: center; + justify-content: left; + + & .author { + flex: unset; + gap: 0; + + & .name { + display: flex; + align-items: center; + + & .p { + font-family: "Source Code Pro"; + } + } + } + + & .date { + color: grey; + } + + } + + & footer { + justify-content: left; + margin: unset; + + & .icon { + margin: 0; + align-items: center; + gap: 0.2rem; + width: 64px; + + & img { + height: 18px; + } + + & .react-img { + height: 24px; + } + + & .react-icon { + font-size: 20px; + } + + & span { + margin-right: unset; + text-align: left; + font-size: 14px; + line-height: 1rem; + color: grey; + width: unset; + } + } + + & .menu-icon { + margin-left: auto; + } + } + } + + + & .user-contact { + & .contact-cover { + margin-bottom: -40px; + + & img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + & .contact-name { + display: flex; + align-items: center; + gap: 0.5rem; + } + + & .contact-username { + margin-top: 1rem; + font-family: "Source Code Pro"; + font-weight: 400; + } + + & button { + width: unset; + margin: unset; + height: unset; + } + } + } + + & button { + font-size: 0.9rem; + font-weight: 700; + line-height: 1rem; + border: none; + border-radius: 2rem; + padding: 0.5rem 2rem; + } + + & .sigil, + & .sigil svg { + border-radius: 0.5rem; + } +} + +#big-button { + position: absolute; + right: 2rem; + bottom: 2rem; + font-size: 1.5rem; + font-weight: bold; + cursor: pointer; + text-align: center; + line-height: 3rem; + width: 3rem; + height: 3rem; + border-radius: 50%; + z-index: 100; +} + +/* modal */ +#modal-background { + height: 100vh; + width: 100vw; + background-color: rgb(0, 0, 0, 0.9); + position: fixed; + top: 0; + left: 0; + z-index: 100; +} + +#modal { + position: fixed; + top: 50%; + left: 50%; + width: 80%; + z-index: 101; + transform: translate(-50%, -50%); + background-color: var(--background-color); + padding: 1rem; + max-height: 80%; +} + +.modal-buttons { + display: flex; + justify-content: space-around; +} + +::-webkit-scrollbar { + display: none; +}
\ No newline at end of file diff --git a/front/src/styles/trill.css b/front/src/styles/trill.css new file mode 100644 index 0000000..5687c7a --- /dev/null +++ b/front/src/styles/trill.css @@ -0,0 +1,612 @@ +#not-found { + margin: auto; + padding: 2rem; + text-align: center; +} + +#not-found button { + height: 1.5rem; + margin: auto; +} + +#timeline { + overflow-y: auto; + overflow-x: hidden; +} + +.timeline-post { + width: 100%; + border-top: 1px solid var(--text-color); +} + +.trill-post { + display: flex; + padding: 0.5rem; + /* min-height: 150px; */ +} + +.trill-post .author { + flex: 1 0 auto; +} + +.trill-reply-thread { + border-top: 1px solid var(--text-color); +} + +.trill-post:first-child { + border-top: none; +} + +.trill-post:last-child { + border-bottom: 1px solid var(--text-color); +} + +.trill-post .left { + width: 8%; + margin-right: 1rem; +} + +.trill-post .sigil { + height: 42px; + width: 42px; +} + +.trill-post .right { + width: 90%; +} + +.trill-post header { + display: flex; + justify-content: space-between; +} + +.trill-post header p { + margin: 0 0.3rem; +} + +.trill-post .nick { + font-weight: 700; +} + +.trill-post header .p { + font-family: "Courier New", Courier, monospace; + font-weight: 100; + font-size: 1rem; +} + +.trill-post .p-only { + margin: 0.7rem 0.3rem; + font-weight: 700; +} + +.trill-post .p { + /* margin-top: -5px; */ +} + +.trill-post a { + text-decoration: 0; + color: var(--text-color); +} + +.trill-post blockquote { + border-left: 2px solid grey; + margin-left: 0; + padding-left: 0.5rem; + opacity: 70%; +} + +.trill-post .body { + margin: 1rem; + margin-left: 0; +} + +.trill-post-body p span { + /* margin: 0 3px; */ +} + +.trill-post pre { + font-family: "Courier New", Courier, monospace; + background-color: rgb(200, 200, 200, 0.5); + padding: 0.2rem; + max-width: 90%; + border: 1px solid var(--text-color); + overflow: scroll; +} + +.trill-post .quote-in-post .body { + margin: 0; +} + +.quote-in-post svg { + margin-right: 0.5rem; +} + +.trill-post .body-text { + /* font-family: Arial, Helvetica, sans-serif; */ + margin: 0.3rem 0 1rem 0; + word-break: break-word; +} + +.trill-post .trill-post-paragraph { + margin-block-start: 1em; + margin-block-end: 1em; +} + +.trill-post .body-text a { + text-decoration: underline; +} + +.trill-post .token { + margin: 0 0.5rem; +} + +.trill-post .date { + float: right; +} + +.trill-post .nav { + display: flex; +} + +.trill-post .chevron { + width: 1.5rem; + height: 1.5rem; +} + +.body-media { + width: 100%; + max-height: 520px; + text-align: center; + /* images being inline */ +} + +.body-media img { + margin: 1px 3px; +} + +.body-img-1-of-1 { + max-width: 100%; + max-height: inherit; + margin: auto !important; +} + +.body-img-1-of-2 { + max-width: 48.5%; + max-height: inherit; +} + +.body-img-1-of-3 { + max-width: 48.5%; +} + +.body-img-1-of-4 { + max-width: 48.5%; +} + +.body-img-1-of-5 { + max-width: 31%; +} + +.body-img-1-of-6 { + max-width: 31%; +} + +.body-img-1-of-7 { + max-width: 31%; +} + +.body-img-1-of-8 { + max-width: 31%; +} + +.body-img-1-of-9 { + max-width: 31%; +} + +/* quotes */ + +.quote-in-post { + margin-top: 1rem; + padding: 0.5rem; + border: 1px solid grey; + border-radius: 0.5rem; + cursor: pointer; +} + +.quote-in-post header { + display: flex; +} + +.mention { + font-family: "Courier New", Courier, monospace; + font-weight: 700; +} + +.mention:hover { + cursor: pointer; + text-decoration: underline; +} + +.bad-quote { + border: 1px solid var(--text-color); + padding: 7px; + border-radius: 0.5rem; +} + +/* post-cards */ +.trill-post-card { + position: relative; + border-radius: 0.3rem; + /* margin: 1rem 0 0 -8%; */ + margin: 0.5rem 0; +} + +.trill-post-card-logo { + position: absolute; + width: 25px; + height: 25px; + top: -17px; + left: -17px; +} + +#post-menu { + position: absolute; + top: 0; + right: 50px; + z-index: 99; +} + +.deleted-post { + text-align: center; + border: 1px solid var(--text-color); + padding: 0.4rem; +} + +#post-menu p { + background-color: var(--background-color); + margin: 0; + padding: 0.5rem; + cursor: pointer; + border: 1px solid var(--text-color); + height: 40px; +} + +#post-menu p:hover { + /* background-color: var(--highlighted-grey); */ +} + +/* threads */ +.trill-reply-thread { + margin-top: 1rem; +} + +#replies>.trill-post:first-child { + border-top: 1px solid black; +} + +/* footer */ + +.footer-wrapper { + position: relative; + /* transform: rotate(0deg); */ + /* the dummy transform enforces position fixed inheritance */ +} + +.post-footer footer { + display: flex; + margin-left: -20px; + height: 24px; + justify-content: space-between; +} + +footer .icon { + cursor: pointer; + margin: 0 0.2rem; + display: flex; + /* min-width: 64px; */ +} + +footer #menu-icon { + width: 32px !important; + /* margin-left: 20px; */ +} + +.post-footer footer .icon img { + display: block; + width: 24px; + height: 24px; +} + +footer .icon span { + display: block; + width: 30px; + text-align: right; + padding-top: 0.2rem; + margin-right: 0.4rem; +} + +footer .icon span:hover { + text-decoration: underline; +} + +.react-icon { + font-size: 26px; + margin: -10px 0 0 0 !important; + padding: 0; + padding-top: 0 !important; +} + +#react-list { + display: flex; + flex-wrap: wrap; +} + +#react-list img { + margin: 3px; + width: 50px; + height: 50px; + cursor: pointer; + border: 1px solid transparent; +} + +#react-list span { + width: 50px; + height: 50px; + font-size: 38px; + margin: 3px; + cursor: pointer; + border: 1px solid transparent; +} + +#react-list span:hover, +#react-list img:hover { + border: 1px solid var(--text-color); +} + +#menu-background { + position: fixed; + top: 0; + left: 0; + opacity: 0; + height: 100vh; + width: 100vw; +} + +/* contact */ + +.contact-cover { + height: 150px; + max-width: 100vw; + margin-bottom: -50px; +} + +#contact-proper { + padding: 1rem; +} + +#contact-proper .row { + display: flex; +} + +.contact-avatar { + width: 6rem; + height: 6rem; +} + +.contact-name { + margin-top: 1rem; + margin-bottom: 0.5rem; + margin-left: 0.3rem; + font-weight: 700; + font-size: 1.1rem; +} + +.contact-username { + margin-top: -10px; +} + +#contact-proper .buttons { + margin-top: 2rem; + margin-left: auto; +} + +#contact-proper .buttons button { + width: 5rem; + margin-bottom: 5px; + height: 1.5rem; +} + +#contact-proper .p { + font-family: "Courier New", Courier, monospace; +} + +#contact-proper .p-only { + margin-top: 1rem; +} + +.bio-row { + display: flex; + align-items: center; +} + +.stats-row { + display: flex; + justify-content: center; +} + +.stats-icon { + margin: 0 2px; +} + +.stats-row p { + text-align: center; + font-size: 1.3rem; + margin: -5px 0 0 0; +} + +.stats-row img { + width: 32px; +} + +.locked-notice, +.suspended-notice { + text-align: center; +} + +.cover-placeholder { + height: 150px; + background-color: rgb(125, 125, 125, 0.5); +} + +#stats-modal .trill-post { + border-bottom: 1px solid var(--text-color) !important; +} + +#stats-modal { + height: 80vh; +} + +#stats-modal #engagement { + min-height: 40%; + max-height: 40%; + overflow-y: scroll; +} + +#stats-modal .trill-post { + max-height: 50%; + overflow-y: scroll; +} + +.btw { + display: flex; + justify-content: space-between; + align-items: center; +} + +#stats-modal .react-stat img { + width: 32px; + height: 32px; +} + +#stats-modal .react-stat react-icon { + width: 32px; + height: 32px; +} + +#stats-modal #engagement .nickname { + font-size: 1rem; +} + +#stats-modal #engagement .p { + font-size: 0.9rem; +} + +#stats-modal .tab h4 { + font-weight: 100; +} + +#stats-modal .tab.active-tab h4 { + font-weight: 700; +} + +/* .not-found { + border: 1px solid var(--text-color); + border-radius: 1rem; + padding: 0.5rem; +} */ + +/* refs */ +.reference {} + +/* polls */ +.trill-poll { + /* border: 1px solid var(--text-color); */ + border-radius: 1rem; + padding: 0.5rem; + position: relative; + background: linear-gradient(90deg, + rgba(255, 255, 168, 0.4) 0%, + /* Lighter yellow */ + rgba(255, 233, 150, 0.5) 52%, + /* Mid-tone gold */ + rgba(255, 209, 0, 0.4) 100% + /* Deeper gold */ + ); +} + +.trill-poll .poll-option { + height: 2rem; + align-items: center; + text-align: center; + border: 1px solid var(--text-color); + border-radius: 0.7rem; + margin: 1rem; + position: relative; + outline: 3px solid transparent; +} + +.trill-poll .my-vote:hover { + /* cursor:not-allowed */ +} + +.trill-poll .poll-option:hover { + opacity: 50%; + outline-color: var(--text-color); +} + +.trill-poll .poll-option p { + padding: 0 0.5rem; + margin: 0; + line-height: 2rem; +} + +.trill-poll .poll-option-stats { + height: 2rem; + position: relative; +} + +.trill-poll .poll-option-bar { + height: 100%; + position: absolute; + background-color: rgb(100, 100, 100, 0.3); + border-radius: 0.7rem; +} + +.trill-poll .my-vote { + border: 3px solid var(--text-color); + border-right: 4px solid var(--text-color); +} + +.trill-poll .bottom-row { + opacity: 60%; +} + +.youtube-thumbnail { + width: 70%; + margin: 0.7rem auto; +} + +.cursor-button { + width: 100%; + padding: 1rem; + border-top: 1px solid var(--text-color); +} + +.cursor-button button { + display: block; + margin: auto; + padding: 0.5rem; +} + +.rumor-quote img { + width: 50px; + margin-right: 1rem; +} + +#trill-thread { + flex-grow: 1; + height: 100%; + overflow-y: auto; + + +}
\ No newline at end of file diff --git a/front/src/types/nostr.ts b/front/src/types/nostr.ts new file mode 100644 index 0000000..0ccfaf3 --- /dev/null +++ b/front/src/types/nostr.ts @@ -0,0 +1,11 @@ +export type Event = { + id: string; // hex, no 0x, 32bytes + pubkey: string; // "" + sig: string; // "", 64 bytes + created_at: number; + kind: number; + tags: Tag[]; + content: string; +}; + +export type Tag = any[]; diff --git a/front/src/types/nostril.ts b/front/src/types/nostril.ts new file mode 100644 index 0000000..65a6194 --- /dev/null +++ b/front/src/types/nostril.ts @@ -0,0 +1,6 @@ +export type UserProfile = { + name: string; + picture: string; // URL + about: string; + other: Record<string, string>; +}; diff --git a/front/src/types/trill.ts b/front/src/types/trill.ts new file mode 100644 index 0000000..e0936ad --- /dev/null +++ b/front/src/types/trill.ts @@ -0,0 +1,420 @@ +import type { Ship } from "./urbit"; + +export type SortugRef = { + type: string; // could call it app... anyway + ship: Ship; + path: string; // `/${string}` +}; + +export type PostID = string; // +export type ID = string; // +export interface PID { + ship: Ship; + id: ID; +} + +export type TrillNode = Poast | FullNode; +export type FullFeed = Record<ID, FullNode>; +export type FlatFeed = Record<ID, Poast>; + +export interface Engagement { + reacts: ReactMap; + quoted: Array<{ pid: PID }>; + shared: Array<{ pid: PID }>; +} +export type ReactMap = Record<Ship, string>; +export interface SentPoast { + host: Ship; + author: Ship; + thread: ID | null; + parent: ID | null; + contents: string; + read: Lock; + write: Lock; + tags: string[]; +} +export type Poast = { + host: Ship; + author: Ship; + thread: ID | null; + parent: ID | null; + read: Lock; + write: Lock; + tags: string[]; + contents: Content; + id: string; + time: number; // not in the backend + children: ID[]; + engagement: Engagement; + tlonRumor?: boolean; + json?: { origin: ExternalApp; content: string }; // for rumor quoting +}; +export type FullNode = Omit<Poast, "children"> & { + children: FullFeed; + prov?: boolean; +}; +export type Content = Block[]; +export type Block = + | Paragraph + | Blockquote + | Heading + | ListBlock + | Codeblock + | Eval + | Media + | Reference + | ExternalContent; + +export type Paragraph = { paragraph: Inline[] }; +export type Blockquote = { blockquote: Inline[] }; +export type Heading = { heading: { text: string; num: number } }; +export type Codeblock = { codeblock: { code: string; lang: string } }; +export type Eval = { hoon: string }; +export type ListBlock = { list: { ordered: boolean; text: Inline[] } }; +export type Media = { media: PostImages | PostVideo | PostAudio }; +export type PostImages = { images: string[] }; +export type PostVideo = { video: string }; +export type PostAudio = { audio: string }; +export type Reference = { ref: { type: string; ship: Ship; path: string } }; + +export type Inline = + | TextInline + | Italic + | Bold + | Strike + | Underline + | Superscript + | Subscript + | Mention + | Codespan + | LinkInline + | Break; +export type TextInline = { text: string }; +export type Italic = { italic: string }; +export type Bold = { bold: string }; +export type Strike = { strike: string }; +export type Underline = { underline: string }; +export type Superscript = { sup: string }; +export type Subscript = { sub: string }; +export type Mention = { ship: Ship }; +// TODO! export type Da = {date: number} +export type Codespan = { codespan: string }; +export type LinkInline = { link: { href: string; show: string } }; +export type Break = { break: null }; + +export type ExternalContent = { + json: { + origin: ExternalApp; + content: string; + }; +}; +export type ExternalApp = "twatter" | "insta" | "anon" | "rumors"; +export interface TwatterReference { + json: { + origin: "twatter"; + content: string; + }; +} +// interface CodeContent { +// code: { +// expression: string; +// output: string[][]; +// }; +// } +// Notifications +export interface Notifications { + engagement: EngagementNotification[]; + unread: Record<Ship, PID[]>; +} +export type Notification = + | EngagementNotification + | FollowNotification + | UnfollowNotification; +export type EngagementNotification = + | ReactNotification + | ReplyNotification + | QuoteNotification + | RepostNotification + | MentionNotification; +export type NotificationData = { ship: Ship; time: number }; +export interface FollowNotification { + follow: NotificationData; +} +export interface UnfollowNotification { + unfollow: NotificationData; +} +export interface ReactNotification { + react: { + pid: PID; + react: string; + } & NotificationData; +} +export interface ReplyNotification { + reply: { + ab: PID; + ad: PID; + } & NotificationData; +} +export interface QuoteNotification { + quote: { + ab: PID; + ad: PID; + } & NotificationData; +} +export interface RepostNotification { + share: { + ab: PID; + ad: PID; + } & NotificationData; +} +export interface MentionNotification { + mention: { + pid: PID; + } & NotificationData; +} +export interface UnreadDisplay { + [s: Ship]: string[]; +} + +// data fetching +export type MixFeedScry = MixFeed | { bucun: string }; + +export type Cursor = string | null; +export type FC = { + feed: FlatFeed; + start: Cursor; + end: Cursor; +}; +export type MixFeed = { + mix: { + name: string; + fc: FC; + }; +}; +export type PoastScry = { post: Poast } | Bucun | NotFollowScry; +export type Bucun = { bucun: PID }; +// TODO bucun no-node come on +export type UserFeedScry = UserScry | NotFollowScry; +export type NotFollowScry = { bugen: Ship }; +export interface UserScry { + feed: { + ship: Ship; + fc: FC; + }; +} + +export type FullNodeScry = + | { fpost: FullNode } + | { "no-node": { ship: Ship; id: ID } }; + +// Facts +export type PostFact = { + post: ThreadFact | GossipFact; +}; +export type ThreadFact = { thread: FullNode }; +export type GossipFact = { gossip: { post: FullNode; feeds: string[] } }; + +export type PullFact = PeekFact | BegFact; +export type PeekFact = { peek: any }; +export type BegFact = { beg: any }; +export type HarkFact = any; +export type ListsFact = any; + +export type TrillProfile = {}; + +export type TrillPostPermisssion = + | "everyone" + | "planets" + | "followers" + | "pals" + | "tag"; + +// Lists + +export type List = { + name: string; + symbol: string; // @tas + public: boolean; + desc: string; + members: ListEntry[]; + icon: string; + cover: string; +}; +export type ListEntry = { + service: "trill" | "twatter" | "twitter"; + username: string; +}; + +export type Lock = { + rank: { caveats: Rank[]; locked: boolean; public: boolean }; + luk: { caveats: Ship[]; locked: boolean; public: boolean }; + ship: { caveats: Ship[]; locked: boolean; public: boolean }; + tags: { caveats: string[]; locked: boolean; public: boolean }; + custom: { fn: null; public: boolean }; +}; +export type Rank = "czar" | "king" | "duke" | "earl" | "pawn"; +// Fetch return types +export type PushState = { + followers: Ship[]; + gate: { + lock: Lock; + begs: Ship[]; + postBegs: PID[]; + mute: Lock; + backlog: number; + }; +}; + +export type PullState = { + begs: Ship[]; + postBegs: PID[]; + following: Ship[]; +}; +export type TrillSearchResponse = { + search: { + query: string; + fc: FC; + }; +}; +export type ListsResponse = { + lists: List[]; +}; +export type MetaPeek = { + posts: number; + inc: Ship[]; + out: Ship[]; + lock: Lock; + ship: Ship; +}; +export type NodePeek = {}; +export type FeedPeek = { + ship: Ship; + feed: FlatFeed; +}; +export interface FollowAttempt { + ship: Ship; + timestamp: number; +} +export interface Key { + ship: Ship; + name: string; +} +// pals stuff +// TODO +// export interface SocialData { +// groups: any | null; +// clubs: any | null; +// lists: List[]; +// pals: Pals | null; +// contacts: Contacts; +// } + +export type Poll = { + host: Ship; + id: string; // atom id + expiry: number; + text: string; + options: string[]; + votes: PollVotes; + // TODO locks +}; +export type PollVotes = HiddenVotes | OpenExcVotes | OpenIncVotes; +export type HiddenVotes = { + type: "hid"; + exc: boolean; + votes: Record<number, number>; +}; +export type OpenExcVotes = { + type: "exc"; + votes: Record<Ship, VoteComment>; +}; +export type OpenIncVotes = { + type: "inc"; + votes: Record<number, Ship[]>; +}; +export type VoteComment = { option: number; comment: string }; + +export type PollPoke = + | CreatePoll + | CancelPoll + | ChangeExpiry + | VotePoke + | CancelVote + | PeekPoll; +export type PeekPoll = { peek: PID }; +export type SentPoll = { + text: string; + expiry: number; + options: string[]; + exc: boolean; + hidden: boolean; + private: boolean; + id: string; +}; +export type CreatePoll = { + propose: SentPoll; +}; +export type CancelPoll = { + cancel: ID; +}; +export type ChangeExpiry = { + "change-expiry": { + pid: PID; + expiry: number; + }; +}; +export type VotePoke = { + vote: { + pid: PID; + option: number; + comment: string; + }; +}; +export type CancelVote = { + "cancel-vote": { pid: PID; option: number }; +}; +export type PollScry = OnePoll | DonePolls | CurrentPolls | BadPoll; +export type OnePoll = { poll: Poll }; +export type DonePolls = { done: Poll[] }; +export type CurrentPolls = { cur: Poll[] }; +export type BadPoll = { ng: null }; +export type TombPoll = { tomb: null }; + +export type PollUpdate = NewPollU | DedPollU | OldPollU | PollPeekRes; +export type DedPollU = { "ded-poll": PID }; +export type OldPollU = { pid: PID } & ( + | NewVoteU + | PollExpiryChanged + | VoteCanceled + | PollPeekRes +); +export type PollPeekRes = { + "peek-res": PollPeekOK | PollPeekNG | PollPeekNF; +}; +export type PollPeekOK = { + "peek-ok": Poll; +}; +export type PollPeekNG = { "peek-ng": string }; +export type PollPeekNF = { "no-poll": null }; + +export type NewPollU = { + "new-poll": Poll; +}; +export type NewVoteU = { + type: "new-vote"; + update: { + option: number; + ship: Ship; + comment: string; + }; +}; +export type PollExpiryChanged = { + type: "expiry-changed"; + update: { + expiry: number; + }; +}; +export type VoteCanceled = { + type: "vote-canceled"; + update: { option: number; ship: Ship }; +}; diff --git a/front/src/types/twatter.ts b/front/src/types/twatter.ts new file mode 100644 index 0000000..9814cbf --- /dev/null +++ b/front/src/types/twatter.ts @@ -0,0 +1,336 @@ +import type { Ship } from "./urbit"; +import type { Content as TrillContent } from "@/types/trill"; + +export interface APITweet { + core: APITweetCore; + legacy: APITweetLegacy; + rest_id?: string; // number + __typename?: string; + card?: any; + quoted_status_result?: { result: APIQuoteTweet }; +} +export interface APIQuoteTweet extends APITweet { + quotedRefResult: { result: { rest_id: string; __typename: string } }; +} +export interface APITwitterPoll { + binding_values: any[]; + card_platform: any; + name: string; + url: string; + user_refs_results: any[]; +} +export interface UserEntities { + description: { + urls: any[]; + }; + url: { + urls: URLEntity[]; + }; +} +export interface TweetEntities { + user_mentions: UserMentionEntity[]; + urls: URLEntity[]; + hashtags: HashtagEntity[]; + symbols: any[]; + media?: MediaEntity[]; +} +export interface UserMentionEntity { + id_str: string; // "144930676" + indices: [number, number]; + name: string; // "Naninizhoni" + screen_name: string; // "naninizhoni" +} +export interface HashtagEntity { + indices: [number, number]; + text: string; +} +export interface URLEntity { + url: string; + display_url: string; + expanded_url: string; + indices: [number, number]; +} +export interface MediaEntity { + display_url: string; // "pic.twitter.com/0qkz8kpFPQ" + expanded_url: string; // "https://twitter.com/ThaiNewsReports/status/1476368702924898304/photo/1" + media_url_https: string; // "https://pbs.twimg.com/media/FH0dgqeXEAEHVgI.jpg" + url: string; // "https://t.co/0qkz8kpFPQ" + features: { + large: { faces: any[] }; + medium: { faces: any[] }; + orig: { faces: any[] }; + small: { faces: any[] }; + }; + id_str: string; + indices: [number, number]; + original_info: { + height: number; + width: number; + focus_rects?: { x: number; y: number; w: number; h: number }[]; + }; + sizes: { + large: MediaSize; + medium: MediaSize; + small: MediaSize; + thumb: MediaSize; + }; + type: "photo"; //"photo" | ?? +} +export interface ExtendedEntity { + media: ExtendedMediaEntity[] | VideoEntity[]; +} +export interface ExtendedMediaEntity { + display_url: string; // "pic.twitter.com/0qkz8kpFPQ" + expanded_url: string; // "https://twitter.com/ThaiNewsReports/status/1476368702924898304/photo/1" + media_url_https: string; // "https://pbs.twimg.com/media/FH0dgqeXEAEHVgI.jpg" + url: string; // "https://t.co/0qkz8kpFPQ" + features: { + large: { faces: any[] }; + medium: { faces: any[] }; + orig: { faces: any[] }; + small: { faces: any[] }; + }; + id_str: string; + media_key: string; // "3_1476368699842039809" + indices: [number, number]; + original_info: { + height: number; + width: number; + focus_rects?: { x: number; y: number; w: number; h: number }[]; + }; + sizes: { + large: MediaSize; + medium: MediaSize; + small: MediaSize; + thumb: MediaSize; + }; + type: "photo" | "video"; // ?? + ext_media_availability: { status: string }; // "Available" + ext_media_color: { + palette: { + percentage: number; + rgb: { red: number; blue: number; green: number }; + }[]; + }; +} +export interface VideoEntity extends ExtendedMediaEntity { + original_info: { height: number; width: number }; + additional_media_info: { monetizable: boolean }; + mediaStats: { viewCount: number }; + video_info: { + aspect_ratio: [number, number]; + duration_millis: number; + variants: VideoVariant[]; + }; +} +export interface VideoVariant { + bitrate?: number; + content_type: string; // "video/mp4" "application/x-mpegURL" + url: string; // "https://video.twimg.com/ext_tw_video/1476257027378888711/pu/vid/640x360/KwFE_5vWD7hAVtu4.mp4?tag=12" +} +export interface MediaSize { + h: number; + w: number; + resize: "crop" | "fit"; +} +export interface APITweetLegacy { + conversation_id_str: string; // thread id + created_at: string; // "Wed Dec 15 14:02:32 +0000 2021" + display_text_range: [number, number]; // [0, 96] + entities: TweetEntities; + favorite_count: number; + favorited: boolean; + full_text: string; // + id_str: string; // "1471118482095943680" + is_quote_status: boolean; + lang: string; // "en" + possibly_sensitive: boolean; + possibly_sensitive_editable: boolean; + quote_count: number; + reply_count: number; + retweet_count: number; + retweeted: boolean; + source: string; // "<a href=\"https://mobile.twitter.com\" rel=\"nofollow\">Twitter Web App</a>" + user_id_str: string; // "368897808" + retweeted_status_result?: { result: APITweet }; + quoted_status_id_str?: string; + quoted_status_permalink?: { + display: string; //"twitter.com/lijukic/status…" + expanded: string; //"https://twitter.com/lijukic/status/1476284640826736640" + url: string; //"https://t.co/1yLiM97600" + }; + in_reply_to_screen_name?: string; + in_reply_to_status_id_str?: string; + in_reply_to_user_id_str?: string; + self_thread?: { id_str: string }; + extended_entities?: ExtendedEntity; +} +export interface Tweet { + index: string; // number + parent: string | null; // number + thread: string; // number + time: number; + author: TweetAuthor; + contents: TwatterToken[]; + text: string; + media: TweetMedia[]; + poll: TwatterPoll | null; + rt_by: TweetAuthor | null; + rt_time: number | null; + language: string; + quoting: Tweet | null; + replies: number; + rts: number; + likes: number; + quotes: number; +} +export type TweetMedia = TweetPic | TweetVideo; +export interface TweetPic { + url: string; //url + thumbnail?: string; //url +} +export interface TweetVideo { + url: string; //url + thumbnail: string; //url +} + +export interface TwatterPoll { + card_url: string; + api: string; + last_updated_datetime_utc: Date; + end_datetime_utc: Date; + counts_are_final: boolean; + choice1_label: string; + choice1_count: string; + choice2_label: string; + choice2_count: string; + choice3_label?: string; + choice3_count?: string; + choice4_label?: string; + choice4_count?: string; +} + +export interface TweetAuthor { + suspended?: boolean; + username: string; + name: string; + id: string; // number + created: number; // date + bio: string; + avatar: string; + avatar_big: string; + cover_img: string; + following: number; + followers: number; + location: string; + url: string; + bluecheck: boolean; + locked: boolean; + withheld_in_countries: string[]; + post_count: number; + media_count: number; + listed_count: number; + patp: Ship | null; +} +export type EntityType = + | "user_mentions" + | "hashtags" + | "urls" + | "media" + | "symbol"; +export type tokenizerData = [string, taggedContent[]]; +export type taggedContent = [string, TwatterToken]; +export type TwatterToken = TwatterContent | EmojiContent | HashtagContent; +export type TwatterContent = + | { text: string } + | { mention: string } + | { url: string } + | { hashtag: string }; +export interface EmojiContent { + emoji: string; +} +export interface HashtagContent { + hashtag: string; +} +export interface TwatterThread { + thread: TweetsWithCursor; + replyThreads: TweetsWithCursor[]; + cursor: string; +} +export interface TweetsWithCursor { + tweets: Tweet[]; + cursor: string; + cursorBottom?: string; + type?: string; +} +export interface APITweetCore { + user: APIUserProfile; +} +export interface APIUserProfile { + affiliates_highlighted_label?: any; + id?: string; // base64 + rest_id: string; // number + legacy: { + created_at: string; // "Tue Sep 06 12:23:27 +0000 2011" + default_profile: boolean; + default_profile_image: boolean; + description: string; + entities: UserEntities; + fast_followers_count: number; + favourites_count: number; + followers_count: number; + friends_count: number; + has_custom_timelines: boolean; + is_translator: boolean; + listed_count: number; + location: string; + media_count: number; + name: string; + normal_followers_count: number; + pinned_tweet_ids_str: string[]; // ['1471118482095943680'] + profile_banner_extensions: any; //{mediaColor: {…}} + profile_banner_url: string; // "https://pbs.twimg.com/profile_banners/368897808/1398230281" + profile_image_extensions: any; // {mediaColor: {…}} + profile_image_url_https: string; //"https://pbs.twimg.com/profile_images/1193225494994571264/So4axAeC_normal.jpg" + profile_interstitial_type: string; // "" + protected: boolean; + screen_name: string; + statuses_count: number; + translator_type: string; // "none" + url?: string; // "https://t.co/uaINnItg4d" + verified: boolean; + withheld_in_countries: string[]; + }; +} + +// return types of our Urbit fetcher +export type NoCokiRes = { "no-coki": null }; +export type BadRequestRes = { fail: string }; +export type TwatterSearchRes = TwatterSearchResOK | NoCokiRes | BadRequestRes; +export type TwatterUserRes = TwatterUserResOK | NoCokiRes | BadRequestRes; +export type TwatterThreadRes = TwatterThreadResOK | NoCokiRes | BadRequestRes; +export type TwatterUserResOK = { + user: { + profile: string; + feed: string; + }; +}; +export type TwatterThreadResOK = TwatterLoggedThreadRes | TwatterLurkThreadRes; +export type TwatterLurkThreadRes = { + "thread-lurk": string; +}; +export type TwatterLoggedThreadRes = { + thread: string; +}; +export type TwatterSearchResOK = { + search: { + query: string; + data: string; + } +} +export type TwatterNotification = { + type: string; + user: string; + post?: string; + text: string; +}
\ No newline at end of file diff --git a/front/src/types/ui.ts b/front/src/types/ui.ts new file mode 100644 index 0000000..d964d84 --- /dev/null +++ b/front/src/types/ui.ts @@ -0,0 +1,49 @@ +import {Poast } from "./trill"; +import { Tweet } from "./twatter"; +import { Ship } from "./urbit"; + +export type Timestamp = number; +export type UrbitTime = string; + +export interface ComposerData { + type: "quote" | "reply"; + post: SPID; +} +export type SPID = TrillPID | TwatterPID | RumorsPID; + +export interface TrillPID { + service: "trill"; + post: Poast; +} +export interface TwatterPID { + service: "twatter"; + post: Tweet; +} +export interface RumorsPID { + service: "rumors"; + post: Poast +} +export interface Guanxi { + trill: Relationship; + pals: Relationship; +} +export type Relationship = "mutual" | "incoming" | "outgoing" | "none"; + +// should make a sortug type codebase + +export type BucketCreds = { + opts: { + bucket: string; + origin: string; // this is the endpoint + region: string; + }, creds: { + credentials: { + accessKey: string; + secretKey: string; + } + } +} + +export type DateStruct = {year: number, month: number, day: number} +export type ChatQuoteParams = {p: Ship, nest: string, id: string} +export type ReactGrouping = Array<{react: string, ships: Ship[]}>
\ No newline at end of file diff --git a/front/src/types/urbit.ts b/front/src/types/urbit.ts new file mode 100644 index 0000000..af9ee06 --- /dev/null +++ b/front/src/types/urbit.ts @@ -0,0 +1,8 @@ +export type Ship = string; +export interface S3Bucket { + accessKeyId: string; + endpoint: string; + secretAccessKey: string; + bucket: string; + region: string; +}
\ No newline at end of file diff --git a/front/src/vite-env.d.ts b/front/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/front/src/vite-env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite/client" /> diff --git a/front/tsconfig.app.json b/front/tsconfig.app.json new file mode 100644 index 0000000..873ffa5 --- /dev/null +++ b/front/tsconfig.app.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { "@/*": ["./src/*"] }, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/front/tsconfig.json b/front/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/front/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/front/tsconfig.node.json b/front/tsconfig.node.json new file mode 100644 index 0000000..f85a399 --- /dev/null +++ b/front/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/front/vite.config.ts b/front/vite.config.ts new file mode 100644 index 0000000..e2b7add --- /dev/null +++ b/front/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { resolve } from "node:path"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@": resolve(__dirname, "./src"), + }, + }, +}); |