pds dash for shimaenaga.veryroundbird.house (based off of pds.witchcraft.systems)

add files from witchcraft.systems pds frontpage + edits

+155
.gitignore
···
···
+
# Logs
+
logs
+
*.log
+
npm-debug.log*
+
yarn-debug.log*
+
yarn-error.log*
+
pnpm-debug.log*
+
lerna-debug.log*
+
+
node_modules
+
dist
+
dist-ssr
+
*.local
+
+
# Editor directories and files
+
.vscode/*
+
!.vscode/extensions.json
+
.idea
+
.DS_Store
+
*.suo
+
*.ntvs*
+
*.njsproj
+
*.sln
+
*.sw?
+
+
# Diagnostic reports (https://nodejs.org/api/report.html)
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+
# Runtime data
+
pids
+
*.pid
+
*.seed
+
*.pid.lock
+
+
# Directory for instrumented libs generated by jscoverage/JSCover
+
lib-cov
+
+
# Coverage directory used by tools like istanbul
+
coverage
+
*.lcov
+
+
# nyc test coverage
+
.nyc_output
+
+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+
.grunt
+
+
# Bower dependency directory (https://bower.io/)
+
bower_components
+
+
# node-waf configuration
+
.lock-wscript
+
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
+
build/Release
+
+
# Dependency directories
+
node_modules/
+
jspm_packages/
+
+
# Snowpack dependency directory (https://snowpack.dev/)
+
web_modules/
+
+
# TypeScript cache
+
*.tsbuildinfo
+
+
# Optional npm cache directory
+
.npm
+
+
# Optional eslint cache
+
.eslintcache
+
+
# Optional stylelint cache
+
.stylelintcache
+
+
# Microbundle cache
+
.rpt2_cache/
+
.rts2_cache_cjs/
+
.rts2_cache_es/
+
.rts2_cache_umd/
+
+
# Optional REPL history
+
.node_repl_history
+
+
# Output of 'npm pack'
+
*.tgz
+
+
# Yarn Integrity file
+
.yarn-integrity
+
+
# dotenv environment variable files
+
.env
+
.env.development.local
+
.env.test.local
+
.env.production.local
+
.env.local
+
+
# parcel-bundler cache (https://parceljs.org/)
+
.cache
+
.parcel-cache
+
+
# Next.js build output
+
.next
+
out
+
+
# Nuxt.js build / generate output
+
.nuxt
+
dist
+
+
# Gatsby files
+
.cache/
+
# Comment in the public line in if your project uses Gatsby and not Next.js
+
# https://nextjs.org/blog/next-9-1#public-directory-support
+
# public
+
+
# vuepress build output
+
.vuepress/dist
+
+
# vuepress v2.x temp and cache directory
+
.temp
+
.cache
+
+
# vitepress build output
+
**/.vitepress/dist
+
+
# vitepress cache directory
+
**/.vitepress/cache
+
+
# Docusaurus cache and generated files
+
.docusaurus
+
+
# Serverless directories
+
.serverless/
+
+
# FuseBox cache
+
.fusebox/
+
+
# DynamoDB Local files
+
.dynamodb/
+
+
# TernJS port file
+
.tern-port
+
+
# Stores VSCode versions used for testing VSCode extensions
+
.vscode-test
+
+
# yarn v2
+
.yarn/cache
+
.yarn/unplugged
+
.yarn/build-state.yml
+
.yarn/install-state.gz
+
.pnp.*
+
+
# Config files
+
config.ts
+21
LICENSE
···
···
+
# MIT License
+
+
Copyright (c) 2025 Witchcraft Systems
+
+
Permission is hereby granted, free of charge, to any person obtaining a copy
+
of this software and associated documentation files (the "Software"), to deal
+
in the Software without restriction, including without limitation the rights
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the Software is
+
furnished to do so, subject to the following conditions:
+
+
The above copyright notice and this permission notice shall be included in all
+
copies or substantial portions of the Software.
+
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+
SOFTWARE.
+59
README.md
···
···
+
# pds-dash
+
+
a frontend dashboard with stats for your ATProto PDS.
+
+
## setup
+
+
### prerequisites
+
+
- [deno](https://deno.com/manual/getting_started/installation)
+
+
### installing
+
+
clone the repo, copy `config.ts.example` to `config.ts` and edit it to your liking.
+
+
then, install dependencies using deno:
+
+
```sh
+
deno install
+
```
+
+
### development server
+
+
local develompent server with hot reloading:
+
+
```sh
+
deno task dev
+
```
+
+
### building
+
+
to build the optimized bundle run:
+
+
```sh
+
deno task build
+
```
+
+
the output will be in the `dist/` directory.
+
+
## deploying
+
+
we use our own CI/CD workflow at [`.forgejo/workflows/deploy.yaml`](.forgejo/workflows/deploy.yaml), but it boils down to building the project bundle and deploying it to a web server. it'll probably make more sense to host it on the same domain as your PDS, but it doesn't affect anything if you host it somewhere else.
+
+
## configuring
+
+
[`config.ts`](config.ts) is the main configuration file, you can find more information in the file itself.
+
+
## theming
+
+
themes are located in the `themes/` directory, you can create your own theme by copying one of the existing themes and modifying it to your liking.
+
+
currently, the name of the theme is determined by the directory name, and the theme itself is defined in `theme.css` inside that directory.
+
+
you can switch themes by changing the `theme` property in `config.ts`.
+
+
the favicon is located at [`public/favicon.ico`](public/favicon.ico)
+
+
## license
+
+
MIT
+223
bun.lock
···
···
+
{
+
"lockfileVersion": 1,
+
"workspaces": {
+
"": {
+
"name": "web",
+
"dependencies": {
+
"@atcute/bluesky": "^2.0.2",
+
"@atcute/client": "^3.0.1",
+
"@atcute/identity-resolver": "^0.1.2",
+
"moment": "^2.30.1",
+
"mutex-ts": "^1.2.1",
+
"svelte-infinite-loading": "^1.4.0",
+
},
+
"devDependencies": {
+
"@sveltejs/vite-plugin-svelte": "^5.0.3",
+
"@tsconfig/svelte": "^5.0.4",
+
"svelte": "^5.23.1",
+
"svelte-check": "^4.1.5",
+
"typescript": "~5.7.2",
+
"vite": "^6.3.1",
+
},
+
},
+
},
+
"packages": {
+
"@atcute/bluesky": ["@atcute/bluesky@2.1.1", "", { "peerDependencies": { "@atcute/client": "^3.0.0" } }, "sha512-wEZfFW58J6yC1SqHcVJOn4qbHENTTzjeCEWthRT5HvKovADLqk54HSMSAuXDMBUbintSTBr0khQNZQ3ZdgzDdQ=="],
+
+
"@atcute/client": ["@atcute/client@3.1.0", "", {}, "sha512-+rQPsHXSf0DUm8XoHoaH7Y2E8tIpbsW84djyPj7dqAyrFIjvGuJ1X1DvMufwbTIcmLerdy+dzl34iZcz/h3Vhg=="],
+
+
"@atcute/identity": ["@atcute/identity@0.1.3", "", { "dependencies": { "@badrap/valita": "^0.4.2" } }, "sha512-ndlD8nypHt8G00wixbozKdSNL0O8HTzBjFGEXeAcBUCXSZPBjRWbqtgyJxhgUWnr7swgxgw1mSbZwRB5b7xCiQ=="],
+
+
"@atcute/identity-resolver": ["@atcute/identity-resolver@0.1.2", "", { "dependencies": { "@atcute/util-fetch": "^1.0.0", "@badrap/valita": "^0.4.2" }, "peerDependencies": { "@atcute/identity": "^0.1.0" } }, "sha512-fP2VbHD04kVcCdNi/Kszo6jFzqM7Pg3p33oGhfp2zVkwFKaVBlwCaFRWEga/Xvu/IDLwNdASGWnLqoA34SFeSg=="],
+
+
"@atcute/util-fetch": ["@atcute/util-fetch@1.0.1", "", { "dependencies": { "@badrap/valita": "^0.4.2" } }, "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow=="],
+
+
"@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="],
+
+
"@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=="],
+
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
+
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.1", "", { "os": "android", "cpu": "arm" }, "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag=="],
+
+
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw=="],
+
+
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.50.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw=="],
+
+
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.50.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw=="],
+
+
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.50.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA=="],
+
+
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.50.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ=="],
+
+
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg=="],
+
+
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw=="],
+
+
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw=="],
+
+
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w=="],
+
+
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q=="],
+
+
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.50.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q=="],
+
+
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ=="],
+
+
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg=="],
+
+
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.50.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg=="],
+
+
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA=="],
+
+
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg=="],
+
+
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.50.1", "", { "os": "none", "cpu": "arm64" }, "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA=="],
+
+
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.50.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ=="],
+
+
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.50.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A=="],
+
+
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.1", "", { "os": "win32", "cpu": "x64" }, "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA=="],
+
+
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
+
+
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.1.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ=="],
+
+
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
+
+
"@tsconfig/svelte": ["@tsconfig/svelte@5.0.5", "", {}, "sha512-48fAnUjKye38FvMiNOj0J9I/4XlQQiZlpe9xaNPfe8vy2Y1hFBt8g1yqf2EGjVvHavo4jf2lC+TQyENCr4BJBQ=="],
+
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
+
+
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
+
+
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
+
+
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
+
+
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
+
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
+
+
"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=="],
+
+
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
+
+
"esrap": ["esrap@2.1.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA=="],
+
+
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
+
+
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
+
+
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
+
+
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
+
+
"moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="],
+
+
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
+
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+
"mutex-ts": ["mutex-ts@1.2.1", "", {}, "sha512-OkcXgf0viuCgYdnm48kiNQ9PzC5OzISQ261svHr/Ybc2vBYC/5xfLXn44hQ+dYRX74v7MCSqV/LKPEbpYdDybw=="],
+
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+
"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=="],
+
+
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
+
+
"rollup": ["rollup@4.50.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.1", "@rollup/rollup-android-arm64": "4.50.1", "@rollup/rollup-darwin-arm64": "4.50.1", "@rollup/rollup-darwin-x64": "4.50.1", "@rollup/rollup-freebsd-arm64": "4.50.1", "@rollup/rollup-freebsd-x64": "4.50.1", "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", "@rollup/rollup-linux-arm-musleabihf": "4.50.1", "@rollup/rollup-linux-arm64-gnu": "4.50.1", "@rollup/rollup-linux-arm64-musl": "4.50.1", "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", "@rollup/rollup-linux-ppc64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-musl": "4.50.1", "@rollup/rollup-linux-s390x-gnu": "4.50.1", "@rollup/rollup-linux-x64-gnu": "4.50.1", "@rollup/rollup-linux-x64-musl": "4.50.1", "@rollup/rollup-openharmony-arm64": "4.50.1", "@rollup/rollup-win32-arm64-msvc": "4.50.1", "@rollup/rollup-win32-ia32-msvc": "4.50.1", "@rollup/rollup-win32-x64-msvc": "4.50.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA=="],
+
+
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
+
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+
"svelte": ["svelte@5.38.10", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-UY+OhrWK7WI22bCZ00P/M3HtyWgwJPi9IxSRkoAE2MeAy6kl7ZlZWJZ8RaB+X4KD/G+wjis+cGVnVYaoqbzBqg=="],
+
+
"svelte-check": ["svelte-check@4.3.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg=="],
+
+
"svelte-infinite-loading": ["svelte-infinite-loading@1.4.0", "", {}, "sha512-Jo+f/yr/HmZQuIiiKKzAHVFXdAUWHW2RBbrcQTil8JVk1sCm/riy7KTJVzjBgQvHasrFQYKF84zvtc9/Y4lFYg=="],
+
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+
+
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
+
+
"vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "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-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="],
+
+
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
+
+
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
+
}
+
}
+605
deno.lock
···
···
+
{
+
"version": "5",
+
"specifiers": {
+
"npm:@atcute/bluesky@^2.0.2": "2.0.2_@atcute+client@3.0.1",
+
"npm:@atcute/client@^3.0.1": "3.0.1",
+
"npm:@atcute/identity-resolver@~0.1.2": "0.1.2_@atcute+identity@0.1.3",
+
"npm:@sveltejs/vite-plugin-svelte@^5.0.3": "5.0.3_svelte@5.28.1__acorn@8.14.1_vite@6.3.2__picomatch@4.0.2",
+
"npm:@tsconfig/svelte@^5.0.4": "5.0.4",
+
"npm:moment@^2.30.1": "2.30.1",
+
"npm:mutex-ts@^1.2.1": "1.2.1",
+
"npm:svelte-check@^4.1.5": "4.1.6_svelte@5.28.1__acorn@8.14.1_typescript@5.7.3",
+
"npm:svelte-infinite-loading@^1.4.0": "1.4.0",
+
"npm:svelte@^5.23.1": "5.28.1_acorn@8.14.1",
+
"npm:typescript@~5.7.2": "5.7.3",
+
"npm:vite@^6.3.1": "6.3.2_picomatch@4.0.2"
+
},
+
"npm": {
+
"@ampproject/remapping@2.3.0": {
+
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+
"dependencies": [
+
"@jridgewell/gen-mapping",
+
"@jridgewell/trace-mapping"
+
]
+
},
+
"@atcute/bluesky@2.0.2_@atcute+client@3.0.1": {
+
"integrity": "sha512-xU+9Rp8bzc9AOnWSc11M1urRppDt3BiWR7v2QrLt3Qoysa5jvL8j2p2w4iRT8vLByz8Q+Xgk5Kz4zWVx1zCiug==",
+
"dependencies": [
+
"@atcute/client"
+
]
+
},
+
"@atcute/client@3.0.1": {
+
"integrity": "sha512-j51SuQYQj5jeKrx1DCXx+Q3fpVvO0JYGnKnJAdDSlesSLjPXjvnx1abC+hkuro58KRHHJvRA6P1MC0pmJsWfcg=="
+
},
+
"@atcute/identity-resolver@0.1.2_@atcute+identity@0.1.3": {
+
"integrity": "sha512-fP2VbHD04kVcCdNi/Kszo6jFzqM7Pg3p33oGhfp2zVkwFKaVBlwCaFRWEga/Xvu/IDLwNdASGWnLqoA34SFeSg==",
+
"dependencies": [
+
"@atcute/identity",
+
"@atcute/util-fetch",
+
"@badrap/valita"
+
]
+
},
+
"@atcute/identity@0.1.3": {
+
"integrity": "sha512-ndlD8nypHt8G00wixbozKdSNL0O8HTzBjFGEXeAcBUCXSZPBjRWbqtgyJxhgUWnr7swgxgw1mSbZwRB5b7xCiQ==",
+
"dependencies": [
+
"@badrap/valita"
+
]
+
},
+
"@atcute/util-fetch@1.0.1": {
+
"integrity": "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow==",
+
"dependencies": [
+
"@badrap/valita"
+
]
+
},
+
"@badrap/valita@0.4.4": {
+
"integrity": "sha512-GEhUCk9c4XbNxi+0YZHZsV4fYNd6HejfWuN4Ti4c02DauX+LyX5WY1Y3WfyZ8Pxxl0zqhs+MLtW98cMh86vv6g=="
+
},
+
"@esbuild/aix-ppc64@0.25.2": {
+
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
+
"os": ["aix"],
+
"cpu": ["ppc64"]
+
},
+
"@esbuild/android-arm64@0.25.2": {
+
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
+
"os": ["android"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/android-arm@0.25.2": {
+
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
+
"os": ["android"],
+
"cpu": ["arm"]
+
},
+
"@esbuild/android-x64@0.25.2": {
+
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
+
"os": ["android"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/darwin-arm64@0.25.2": {
+
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
+
"os": ["darwin"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/darwin-x64@0.25.2": {
+
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
+
"os": ["darwin"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/freebsd-arm64@0.25.2": {
+
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
+
"os": ["freebsd"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/freebsd-x64@0.25.2": {
+
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
+
"os": ["freebsd"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/linux-arm64@0.25.2": {
+
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/linux-arm@0.25.2": {
+
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@esbuild/linux-ia32@0.25.2": {
+
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
+
"os": ["linux"],
+
"cpu": ["ia32"]
+
},
+
"@esbuild/linux-loong64@0.25.2": {
+
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
+
"os": ["linux"],
+
"cpu": ["loong64"]
+
},
+
"@esbuild/linux-mips64el@0.25.2": {
+
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
+
"os": ["linux"],
+
"cpu": ["mips64el"]
+
},
+
"@esbuild/linux-ppc64@0.25.2": {
+
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
+
"os": ["linux"],
+
"cpu": ["ppc64"]
+
},
+
"@esbuild/linux-riscv64@0.25.2": {
+
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
+
"os": ["linux"],
+
"cpu": ["riscv64"]
+
},
+
"@esbuild/linux-s390x@0.25.2": {
+
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
+
"os": ["linux"],
+
"cpu": ["s390x"]
+
},
+
"@esbuild/linux-x64@0.25.2": {
+
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/netbsd-arm64@0.25.2": {
+
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
+
"os": ["netbsd"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/netbsd-x64@0.25.2": {
+
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
+
"os": ["netbsd"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/openbsd-arm64@0.25.2": {
+
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
+
"os": ["openbsd"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/openbsd-x64@0.25.2": {
+
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
+
"os": ["openbsd"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/sunos-x64@0.25.2": {
+
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
+
"os": ["sunos"],
+
"cpu": ["x64"]
+
},
+
"@esbuild/win32-arm64@0.25.2": {
+
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
+
"os": ["win32"],
+
"cpu": ["arm64"]
+
},
+
"@esbuild/win32-ia32@0.25.2": {
+
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
+
"os": ["win32"],
+
"cpu": ["ia32"]
+
},
+
"@esbuild/win32-x64@0.25.2": {
+
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
+
"os": ["win32"],
+
"cpu": ["x64"]
+
},
+
"@jridgewell/gen-mapping@0.3.8": {
+
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+
"dependencies": [
+
"@jridgewell/set-array",
+
"@jridgewell/sourcemap-codec",
+
"@jridgewell/trace-mapping"
+
]
+
},
+
"@jridgewell/resolve-uri@3.1.2": {
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
+
},
+
"@jridgewell/set-array@1.2.1": {
+
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
+
},
+
"@jridgewell/sourcemap-codec@1.5.0": {
+
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+
},
+
"@jridgewell/trace-mapping@0.3.25": {
+
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+
"dependencies": [
+
"@jridgewell/resolve-uri",
+
"@jridgewell/sourcemap-codec"
+
]
+
},
+
"@rollup/rollup-android-arm-eabi@4.40.0": {
+
"integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==",
+
"os": ["android"],
+
"cpu": ["arm"]
+
},
+
"@rollup/rollup-android-arm64@4.40.0": {
+
"integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==",
+
"os": ["android"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-darwin-arm64@4.40.0": {
+
"integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==",
+
"os": ["darwin"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-darwin-x64@4.40.0": {
+
"integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==",
+
"os": ["darwin"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-freebsd-arm64@4.40.0": {
+
"integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==",
+
"os": ["freebsd"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-freebsd-x64@4.40.0": {
+
"integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==",
+
"os": ["freebsd"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-linux-arm-gnueabihf@4.40.0": {
+
"integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@rollup/rollup-linux-arm-musleabihf@4.40.0": {
+
"integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@rollup/rollup-linux-arm64-gnu@4.40.0": {
+
"integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-linux-arm64-musl@4.40.0": {
+
"integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-linux-loongarch64-gnu@4.40.0": {
+
"integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==",
+
"os": ["linux"],
+
"cpu": ["loong64"]
+
},
+
"@rollup/rollup-linux-powerpc64le-gnu@4.40.0": {
+
"integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==",
+
"os": ["linux"],
+
"cpu": ["ppc64"]
+
},
+
"@rollup/rollup-linux-riscv64-gnu@4.40.0": {
+
"integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==",
+
"os": ["linux"],
+
"cpu": ["riscv64"]
+
},
+
"@rollup/rollup-linux-riscv64-musl@4.40.0": {
+
"integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==",
+
"os": ["linux"],
+
"cpu": ["riscv64"]
+
},
+
"@rollup/rollup-linux-s390x-gnu@4.40.0": {
+
"integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==",
+
"os": ["linux"],
+
"cpu": ["s390x"]
+
},
+
"@rollup/rollup-linux-x64-gnu@4.40.0": {
+
"integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-linux-x64-musl@4.40.0": {
+
"integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@rollup/rollup-win32-arm64-msvc@4.40.0": {
+
"integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==",
+
"os": ["win32"],
+
"cpu": ["arm64"]
+
},
+
"@rollup/rollup-win32-ia32-msvc@4.40.0": {
+
"integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==",
+
"os": ["win32"],
+
"cpu": ["ia32"]
+
},
+
"@rollup/rollup-win32-x64-msvc@4.40.0": {
+
"integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==",
+
"os": ["win32"],
+
"cpu": ["x64"]
+
},
+
"@sveltejs/acorn-typescript@1.0.5_acorn@8.14.1": {
+
"integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
+
"dependencies": [
+
"acorn"
+
]
+
},
+
"@sveltejs/vite-plugin-svelte-inspector@4.0.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.28.1___acorn@8.14.1__vite@6.3.2___picomatch@4.0.2_svelte@5.28.1__acorn@8.14.1_vite@6.3.2__picomatch@4.0.2": {
+
"integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==",
+
"dependencies": [
+
"@sveltejs/vite-plugin-svelte",
+
"debug",
+
"svelte",
+
"vite"
+
]
+
},
+
"@sveltejs/vite-plugin-svelte@5.0.3_svelte@5.28.1__acorn@8.14.1_vite@6.3.2__picomatch@4.0.2": {
+
"integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==",
+
"dependencies": [
+
"@sveltejs/vite-plugin-svelte-inspector",
+
"debug",
+
"deepmerge",
+
"kleur",
+
"magic-string",
+
"svelte",
+
"vite",
+
"vitefu"
+
]
+
},
+
"@tsconfig/svelte@5.0.4": {
+
"integrity": "sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q=="
+
},
+
"@types/estree@1.0.7": {
+
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
+
},
+
"acorn@8.14.1": {
+
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+
"bin": true
+
},
+
"aria-query@5.3.2": {
+
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="
+
},
+
"axobject-query@4.1.0": {
+
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="
+
},
+
"chokidar@4.0.3": {
+
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+
"dependencies": [
+
"readdirp"
+
]
+
},
+
"clsx@2.1.1": {
+
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
+
},
+
"debug@4.4.0": {
+
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+
"dependencies": [
+
"ms"
+
]
+
},
+
"deepmerge@4.3.1": {
+
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
+
},
+
"esbuild@0.25.2": {
+
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
+
"optionalDependencies": [
+
"@esbuild/aix-ppc64",
+
"@esbuild/android-arm",
+
"@esbuild/android-arm64",
+
"@esbuild/android-x64",
+
"@esbuild/darwin-arm64",
+
"@esbuild/darwin-x64",
+
"@esbuild/freebsd-arm64",
+
"@esbuild/freebsd-x64",
+
"@esbuild/linux-arm",
+
"@esbuild/linux-arm64",
+
"@esbuild/linux-ia32",
+
"@esbuild/linux-loong64",
+
"@esbuild/linux-mips64el",
+
"@esbuild/linux-ppc64",
+
"@esbuild/linux-riscv64",
+
"@esbuild/linux-s390x",
+
"@esbuild/linux-x64",
+
"@esbuild/netbsd-arm64",
+
"@esbuild/netbsd-x64",
+
"@esbuild/openbsd-arm64",
+
"@esbuild/openbsd-x64",
+
"@esbuild/sunos-x64",
+
"@esbuild/win32-arm64",
+
"@esbuild/win32-ia32",
+
"@esbuild/win32-x64"
+
],
+
"scripts": true,
+
"bin": true
+
},
+
"esm-env@1.2.2": {
+
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="
+
},
+
"esrap@1.4.6": {
+
"integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==",
+
"dependencies": [
+
"@jridgewell/sourcemap-codec"
+
]
+
},
+
"fdir@6.4.4_picomatch@4.0.2": {
+
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+
"dependencies": [
+
"picomatch"
+
],
+
"optionalPeers": [
+
"picomatch"
+
]
+
},
+
"fsevents@2.3.3": {
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+
"os": ["darwin"],
+
"scripts": true
+
},
+
"is-reference@3.0.3": {
+
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+
"dependencies": [
+
"@types/estree"
+
]
+
},
+
"kleur@4.1.5": {
+
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
+
},
+
"locate-character@3.0.0": {
+
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
+
},
+
"magic-string@0.30.17": {
+
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+
"dependencies": [
+
"@jridgewell/sourcemap-codec"
+
]
+
},
+
"moment@2.30.1": {
+
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="
+
},
+
"mri@1.2.0": {
+
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
+
},
+
"ms@2.1.3": {
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+
},
+
"mutex-ts@1.2.1": {
+
"integrity": "sha512-OkcXgf0viuCgYdnm48kiNQ9PzC5OzISQ261svHr/Ybc2vBYC/5xfLXn44hQ+dYRX74v7MCSqV/LKPEbpYdDybw=="
+
},
+
"nanoid@3.3.11": {
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+
"bin": true
+
},
+
"picocolors@1.1.1": {
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+
},
+
"picomatch@4.0.2": {
+
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="
+
},
+
"postcss@8.5.3": {
+
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+
"dependencies": [
+
"nanoid",
+
"picocolors",
+
"source-map-js"
+
]
+
},
+
"readdirp@4.1.2": {
+
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
+
},
+
"rollup@4.40.0": {
+
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
+
"dependencies": [
+
"@types/estree"
+
],
+
"optionalDependencies": [
+
"@rollup/rollup-android-arm-eabi",
+
"@rollup/rollup-android-arm64",
+
"@rollup/rollup-darwin-arm64",
+
"@rollup/rollup-darwin-x64",
+
"@rollup/rollup-freebsd-arm64",
+
"@rollup/rollup-freebsd-x64",
+
"@rollup/rollup-linux-arm-gnueabihf",
+
"@rollup/rollup-linux-arm-musleabihf",
+
"@rollup/rollup-linux-arm64-gnu",
+
"@rollup/rollup-linux-arm64-musl",
+
"@rollup/rollup-linux-loongarch64-gnu",
+
"@rollup/rollup-linux-powerpc64le-gnu",
+
"@rollup/rollup-linux-riscv64-gnu",
+
"@rollup/rollup-linux-riscv64-musl",
+
"@rollup/rollup-linux-s390x-gnu",
+
"@rollup/rollup-linux-x64-gnu",
+
"@rollup/rollup-linux-x64-musl",
+
"@rollup/rollup-win32-arm64-msvc",
+
"@rollup/rollup-win32-ia32-msvc",
+
"@rollup/rollup-win32-x64-msvc",
+
"fsevents"
+
],
+
"bin": true
+
},
+
"sade@1.8.1": {
+
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+
"dependencies": [
+
"mri"
+
]
+
},
+
"source-map-js@1.2.1": {
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
+
},
+
"svelte-check@4.1.6_svelte@5.28.1__acorn@8.14.1_typescript@5.7.3": {
+
"integrity": "sha512-P7w/6tdSfk3zEVvfsgrp3h3DFC75jCdZjTQvgGJtjPORs1n7/v2VMPIoty3PWv7jnfEm3x0G/p9wH4pecTb0Wg==",
+
"dependencies": [
+
"@jridgewell/trace-mapping",
+
"chokidar",
+
"fdir",
+
"picocolors",
+
"sade",
+
"svelte",
+
"typescript"
+
],
+
"bin": true
+
},
+
"svelte-infinite-loading@1.4.0": {
+
"integrity": "sha512-Jo+f/yr/HmZQuIiiKKzAHVFXdAUWHW2RBbrcQTil8JVk1sCm/riy7KTJVzjBgQvHasrFQYKF84zvtc9/Y4lFYg=="
+
},
+
"svelte@5.28.1_acorn@8.14.1": {
+
"integrity": "sha512-iOa9WmfNG95lSOSJdMhdjJ4Afok7IRAQYXpbnxhd5EINnXseG0GVa9j6WPght4eX78XfFez45Fi+uRglGKPV/Q==",
+
"dependencies": [
+
"@ampproject/remapping",
+
"@jridgewell/sourcemap-codec",
+
"@sveltejs/acorn-typescript",
+
"@types/estree",
+
"acorn",
+
"aria-query",
+
"axobject-query",
+
"clsx",
+
"esm-env",
+
"esrap",
+
"is-reference",
+
"locate-character",
+
"magic-string",
+
"zimmerframe"
+
]
+
},
+
"tinyglobby@0.2.13_picomatch@4.0.2": {
+
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+
"dependencies": [
+
"fdir",
+
"picomatch"
+
]
+
},
+
"typescript@5.7.3": {
+
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
+
"bin": true
+
},
+
"vite@6.3.2_picomatch@4.0.2": {
+
"integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==",
+
"dependencies": [
+
"esbuild",
+
"fdir",
+
"picomatch",
+
"postcss",
+
"rollup",
+
"tinyglobby"
+
],
+
"optionalDependencies": [
+
"fsevents"
+
],
+
"bin": true
+
},
+
"vitefu@1.0.6_vite@6.3.2__picomatch@4.0.2": {
+
"integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==",
+
"dependencies": [
+
"vite"
+
],
+
"optionalPeers": [
+
"vite"
+
]
+
},
+
"zimmerframe@1.1.2": {
+
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="
+
}
+
},
+
"workspace": {
+
"packageJson": {
+
"dependencies": [
+
"npm:@atcute/bluesky@^2.0.2",
+
"npm:@atcute/client@^3.0.1",
+
"npm:@atcute/identity-resolver@~0.1.2",
+
"npm:@sveltejs/vite-plugin-svelte@^5.0.3",
+
"npm:@tsconfig/svelte@^5.0.4",
+
"npm:moment@^2.30.1",
+
"npm:mutex-ts@^1.2.1",
+
"npm:svelte-check@^4.1.5",
+
"npm:svelte-infinite-loading@^1.4.0",
+
"npm:svelte@^5.23.1",
+
"npm:typescript@~5.7.2",
+
"npm:vite@^6.3.1"
+
]
+
}
+
}
+
}
+12
index.html
···
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8" />
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
<title>shimaenaga pds @ veryroundbird.house</title>
+
</head>
+
<body>
+
<div id="app"></div>
+
<script type="module" src="/src/main.ts"></script>
+
</body>
+
</html>
+28
package.json
···
···
+
{
+
"name": "web",
+
"private": true,
+
"version": "0.0.0",
+
"type": "module",
+
"scripts": {
+
"dev": "vite",
+
"build": "vite build",
+
"preview": "vite preview",
+
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
+
},
+
"dependencies": {
+
"@atcute/bluesky": "^2.0.2",
+
"@atcute/client": "^3.0.1",
+
"@atcute/identity-resolver": "^0.1.2",
+
"moment": "^2.30.1",
+
"mutex-ts": "^1.2.1",
+
"svelte-infinite-loading": "^1.4.0"
+
},
+
"devDependencies": {
+
"@sveltejs/vite-plugin-svelte": "^5.0.3",
+
"@tsconfig/svelte": "^5.0.4",
+
"svelte": "^5.23.1",
+
"svelte-check": "^4.1.5",
+
"typescript": "~5.7.2",
+
"vite": "^6.3.1"
+
}
+
}
public/favicon.ico

This is a binary file and will not be displayed.

public/fonts/Recursive_VF_1.085.woff2

This is a binary file and will not be displayed.

+104
src/App.svelte
···
···
+
<script lang="ts">
+
import PostComponent from "./lib/PostComponent.svelte";
+
import AccountComponent from "./lib/AccountComponent.svelte";
+
import InfiniteLoading from "svelte-infinite-loading";
+
import { getNextPosts, Post, getAllMetadataFromPds } from "./lib/pdsfetch";
+
import { Config } from "../config";
+
const accountsPromise = getAllMetadataFromPds();
+
import { onMount } from "svelte";
+
+
let posts: Post[] = [];
+
+
let hue: number = 1;
+
const cycleColors = async () => {
+
while (true) {
+
hue += 1;
+
if (hue > 360) {
+
hue = 0;
+
}
+
document.documentElement.style.setProperty("--primary-h", hue.toString());
+
await new Promise((resolve) => setTimeout(resolve, 10));
+
}
+
}
+
let clickCounter = 0;
+
const carameldansenfusion = async () => {
+
clickCounter++;
+
if (clickCounter >= 10) {
+
clickCounter = 0;
+
cycleColors();
+
}
+
};
+
+
onMount(() => {
+
// Fetch initial posts
+
getNextPosts().then((initialPosts) => {
+
posts = initialPosts;
+
});
+
});
+
// Infinite loading function
+
const onInfinite = ({
+
detail: { loaded, complete },
+
}: {
+
detail: { loaded: () => void; complete: () => void };
+
}) => {
+
getNextPosts().then((newPosts) => {
+
console.log("Loading next posts...");
+
if (newPosts.length > 0) {
+
posts = [...posts, ...newPosts];
+
loaded();
+
} else {
+
complete();
+
}
+
});
+
};
+
</script>
+
+
<main>
+
<div id="content">
+
{#await accountsPromise}
+
<p>Loading...</p>
+
{:then accountsData}
+
<div id="account">
+
<pre id="asciiart">
+
█▓░░░░░░░░░▒
+
█▓▒ ░▒
+
▓▒▒▒░ ▒
+
▓▒░░░░ ░▓
+
█▒ ░░ ░ ░ ▓▒ ░█
+
▓░ ░░ ▒▓░░▒▒░ ▒
+
███████ ██▓▒░▒▒░ ░░░░ ░░░ ▒
+
█▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▒▒▓▒▒▒▓░ ▓
+
███▓▒░░▒░░░░▓▓█▓▓▒░ ░
+
▓ ░
+
█ ░ ▒
+
█░ ▒█
+
▒ ▒
+
█▒ █
+
▓ ░█
+
▓▒ ░░ ░▒
+
▓▒▒░░▒▓░ ░░░▒▒▓▓▒▓▓
+
█▓█▓▓█ ███▓█
+
████ ████
+
</pre>
+
<h1 onclick={carameldansenfusion} id="header">shimaenaga pds</h1>
+
<p id="subtitle">a project of <a href="https://veryroundbird.house" target="_blank">veryroundbird.house</a></p>
+
<p>{accountsData.length} birds nesting here</p>
+
<div id="accountsList">
+
{#each accountsData as accountObject}
+
<AccountComponent account={accountObject} />
+
{/each}
+
</div>
+
<footer id="footer">{@html Config.FOOTER_TEXT}</footer>
+
</div>
+
{:catch error}
+
<p>Error: {error.message}</p>
+
{/await}
+
+
<div id="feed">
+
{#each posts as postObject}
+
<PostComponent post={postObject as Post} />
+
{/each}
+
<InfiniteLoading on:infinite={onInfinite} distance={3000} />
+
</div>
+
</div>
+
</main>
+4
src/app.css
···
···
+
@import url('./themes/colors.css');
+
body {
+
background-color: red;
+
}
+37
src/lib/AccountComponent.svelte
···
···
+
<script lang="ts">
+
import type { AccountMetadata } from "./pdsfetch";
+
const { account }: { account: AccountMetadata } = $props();
+
console.log(account);
+
import { Config } from "../../config";
+
</script>
+
+
<button class="accountBtn" type="button" popovertarget="{account.handle.replace('.')}" title="{account.handle}">
+
<div class="accountContainer">
+
{#if account.avatarCid}
+
<img
+
class="avatar"
+
alt="avatar of {account.displayName}"
+
width="24"
+
height="24"
+
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={account.did}&cid={account.avatarCid}"
+
/>
+
{:else}
+
<img
+
class="avatar"
+
alt="no avatar for {account.displayName}"
+
width="24"
+
height="24"
+
src=""
+
/>
+
{/if}
+
</div>
+
<div class="accountTooltip" popover id="{account.handle.replace('.')}">
+
<div class="banner">
+
<img class="bannerImg" src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={account.did}&cid={account.bannerCid}" alt="{account.displayName}'s banner" width="300" height="100" />
+
<img class="avatarInsetImg" src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={account.did}&cid={account.avatarCid}" alt="{account.displayName}'s avatar" width="50" height="50" />
+
</div>
+
<div class="displayName">{account.displayName}</div>
+
<div class="handle"><a href="{Config.FRONTEND_URL}/profile/{account.did}" target="_blank">{account.handle}</a></div>
+
<div class="desc">{account.description}</div>
+
</div>
+
</button>
+156
src/lib/PostComponent.svelte
···
···
+
<script lang="ts">
+
import { Post } from "./pdsfetch";
+
import { Config } from "../../config";
+
import { onMount } from "svelte";
+
import moment from "moment";
+
+
let { post }: { post: Post } = $props();
+
+
// State for image carousel
+
let currentImageIndex = $state(0);
+
+
// Functions to navigate carousel
+
function nextImage() {
+
if (post.imagesCid && currentImageIndex < post.imagesCid.length - 1) {
+
currentImageIndex++;
+
}
+
}
+
+
function prevImage() {
+
if (currentImageIndex > 0) {
+
currentImageIndex--;
+
}
+
}
+
+
// Function to preload an image
+
function preloadImage(index: number): void {
+
if (!post.imagesCid || index < 0 || index >= post.imagesCid.length) return;
+
+
const img = new Image();
+
img.src = `${Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did=${post.authorDid}&cid=${post.imagesCid[index]}`;
+
}
+
+
// Preload adjacent images when current index changes
+
$effect(() => {
+
if (post.imagesCid && post.imagesCid.length > 1) {
+
// Preload next image if available
+
if (currentImageIndex < post.imagesCid.length - 1) {
+
preloadImage(currentImageIndex + 1);
+
}
+
+
// Preload previous image if available
+
if (currentImageIndex > 0) {
+
preloadImage(currentImageIndex - 1);
+
}
+
}
+
});
+
+
// Initial preload of images
+
onMount(() => {
+
if (post.imagesCid && post.imagesCid.length > 1) {
+
// Preload the next image if it exists
+
if (post.imagesCid.length > 1) {
+
preloadImage(1);
+
}
+
}
+
});
+
</script>
+
+
<div class="postContainer">
+
<div class="postHeader">
+
{#if post.authorAvatarCid}
+
<img
+
class="avatar"
+
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post.authorAvatarCid}"
+
alt="avatar of {post.displayName}"
+
/>
+
{/if}
+
<div class="headerText">
+
<a class="displayName" href="{Config.FRONTEND_URL}/profile/{post.authorDid}"
+
>{post.displayName}</a
+
>
+
<p class="handle">
+
<a href="{Config.FRONTEND_URL}/profile/{post.authorHandle}"
+
>@{post.authorHandle}</a
+
>
+
+
<a
+
class="postLink"
+
href="{Config.FRONTEND_URL}/profile/{post.authorDid}/post/{post.recordName}"
+
>{moment(post.timenotstamp).isBefore(moment().subtract(1, "month"))
+
? moment(post.timenotstamp).format("MMM D, YYYY")
+
: moment(post.timenotstamp).fromNow()}</a
+
>
+
</p>
+
</div>
+
</div>
+
<div class="postContent">
+
{#if post.replyingUri}
+
<a
+
class="replyingText"
+
href="{Config.FRONTEND_URL}/profile/{post.replyingUri.repo}/post/{post
+
.replyingUri.rkey}">replying to {post.replyingUri.repo}</a
+
>
+
{/if}
+
{#if post.quotingUri}
+
<a
+
class="quotingText"
+
href="{Config.FRONTEND_URL}/profile/{post.quotingUri.repo}/post/{post
+
.quotingUri.rkey}">quoting {post.quotingUri.repo}</a
+
>
+
{/if}
+
<div class="postText">{post.text}</div>
+
{#if post.imagesCid && post.imagesCid.length > 0}
+
<div id="carouselContainer">
+
<img
+
class="embedImages"
+
alt="Post Image {currentImageIndex + 1} of {post.imagesCid.length}"
+
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post
+
.imagesCid[currentImageIndex]}"
+
/>
+
+
{#if post.imagesCid.length > 1}
+
<div class="carouselControls">
+
<button
+
id="prevBtn"
+
onclick={prevImage}
+
disabled={currentImageIndex === 0}>←</button
+
>
+
<div class="carouselIndicators">
+
{#each post.imagesCid as _, i}
+
<div
+
class="indicator {i === currentImageIndex ? 'active' : ''}"
+
></div>
+
{/each}
+
</div>
+
<button
+
class="nextBtn"
+
onclick={nextImage}
+
disabled={currentImageIndex === post.imagesCid.length - 1}
+
>→</button
+
>
+
</div>
+
{/if}
+
</div>
+
{/if}
+
{#if post.videosLinkCid}
+
<!-- svelte-ignore a11y_media_has_caption -->
+
<video
+
class="embedVideo"
+
src="{Config.PDS_URL}/xrpc/com.atproto.sync.getBlob?did={post.authorDid}&cid={post.videosLinkCid}"
+
controls
+
></video>
+
{/if}
+
{#if post.gifLink}
+
<img
+
class="embedVideo"
+
src="{post.gifLink}"
+
alt="Post GIF"
+
/>
+
{/if}
+
</div>
+
</div>
+
+
<style>
+
+
</style>
+363
src/lib/pdsfetch.ts
···
···
+
import { simpleFetchHandler, XRPC } from "@atcute/client";
+
import "@atcute/bluesky/lexicons";
+
import type {
+
AppBskyActorDefs,
+
AppBskyActorProfile,
+
AppBskyFeedPost,
+
At,
+
ComAtprotoRepoListRecords,
+
} from "@atcute/client/lexicons";
+
import {
+
CompositeDidDocumentResolver,
+
PlcDidDocumentResolver,
+
WebDidDocumentResolver,
+
} from "@atcute/identity-resolver";
+
import { Config } from "../../config";
+
import { Mutex } from "mutex-ts"
+
// import { ComAtprotoRepoListRecords.Record } from "@atcute/client/lexicons";
+
// import { AppBskyFeedPost } from "@atcute/client/lexicons";
+
// import { AppBskyActorDefs } from "@atcute/client/lexicons";
+
+
interface AccountMetadata {
+
did: At.Did;
+
displayName: string;
+
handle: string;
+
avatarCid: string | null;
+
currentCursor?: string;
+
}
+
+
let accountsMetadata: AccountMetadata[] = [];
+
+
interface atUriObject {
+
repo: string;
+
collection: string;
+
rkey: string;
+
}
+
class Post {
+
authorDid: string;
+
authorAvatarCid: string | null;
+
postCid: string;
+
recordName: string;
+
authorHandle: string;
+
displayName: string;
+
text: string;
+
timestamp: number;
+
timenotstamp: string;
+
quotingUri: atUriObject | null;
+
replyingUri: atUriObject | null;
+
imagesCid: string[] | null;
+
videosLinkCid: string | null;
+
gifLink: string | null;
+
+
constructor(
+
record: ComAtprotoRepoListRecords.Record,
+
account: AccountMetadata,
+
) {
+
this.postCid = record.cid;
+
this.recordName = processAtUri(record.uri).rkey;
+
this.authorDid = account.did;
+
this.authorAvatarCid = account.avatarCid;
+
this.authorHandle = account.handle;
+
this.displayName = account.displayName;
+
const post = record.value as AppBskyFeedPost.Record;
+
this.timenotstamp = post.createdAt;
+
this.text = post.text;
+
this.timestamp = Date.parse(post.createdAt);
+
if (post.reply) {
+
this.replyingUri = processAtUri(post.reply.parent.uri);
+
} else {
+
this.replyingUri = null;
+
}
+
this.quotingUri = null;
+
this.imagesCid = null;
+
this.videosLinkCid = null;
+
this.gifLink = null;
+
switch (post.embed?.$type) {
+
case "app.bsky.embed.images":
+
this.imagesCid = post.embed.images.map(
+
(imageRecord: any) => imageRecord.image.ref.$link,
+
);
+
break;
+
case "app.bsky.embed.video":
+
this.videosLinkCid = post.embed.video.ref.$link;
+
break;
+
case "app.bsky.embed.record":
+
this.quotingUri = processAtUri(post.embed.record.uri);
+
break;
+
case "app.bsky.embed.recordWithMedia":
+
this.quotingUri = processAtUri(post.embed.record.record.uri);
+
switch (post.embed.media.$type) {
+
case "app.bsky.embed.images":
+
this.imagesCid = post.embed.media.images.map(
+
(imageRecord) => imageRecord.image.ref.$link,
+
);
+
+
break;
+
case "app.bsky.embed.video":
+
this.videosLinkCid = post.embed.media.video.ref.$link;
+
+
break;
+
}
+
break;
+
case "app.bsky.embed.external": // assuming that external embeds are gifs for now
+
if (post.embed.external.uri.includes(".gif")) {
+
this.gifLink = post.embed.external.uri;
+
}
+
break;
+
}
+
}
+
}
+
+
const processAtUri = (aturi: string): atUriObject => {
+
const parts = aturi.split("/");
+
return {
+
repo: parts[2],
+
collection: parts[3],
+
rkey: parts[4],
+
};
+
};
+
+
const rpc = new XRPC({
+
handler: simpleFetchHandler({
+
service: Config.PDS_URL,
+
}),
+
});
+
+
const getDidsFromPDS = async (): Promise<At.Did[]> => {
+
const { data } = await rpc.get("com.atproto.sync.listRepos", {
+
params: {},
+
});
+
return data.repos.map((repo: any) => repo.did) as At.Did[];
+
};
+
const getAccountMetadata = async (
+
did: `did:${string}:${string}`,
+
) => {
+
const account: AccountMetadata = {
+
did: did,
+
handle: "", // Guaranteed to be filled out later
+
displayName: "",
+
avatarCid: null,
+
};
+
+
try {
+
const { data } = await rpc.get("com.atproto.repo.getRecord", {
+
params: {
+
repo: did,
+
collection: "app.bsky.actor.profile",
+
rkey: "self",
+
},
+
});
+
const value = data.value as AppBskyActorProfile.Record;
+
account.displayName = value.displayName || "";
+
account.description = value.description || "";
+
if (value.avatar) {
+
account.avatarCid = value.avatar.ref["$link"];
+
}
+
if (value.banner) {
+
account.bannerCid = value.banner.ref["$link"];
+
}
+
} catch (e) {
+
console.warn(`Error fetching profile for ${did}:`, e);
+
}
+
+
try {
+
account.handle = await blueskyHandleFromDid(did);
+
} catch (e) {
+
console.error(`Error fetching handle for ${did}:`, e);
+
return null;
+
}
+
+
return account;
+
};
+
+
const getAllMetadataFromPds = async (): Promise<AccountMetadata[]> => {
+
const dids = await getDidsFromPDS();
+
const metadata = await Promise.all(
+
dids.map(async (repo: `did:${string}:${string}`) => {
+
return await getAccountMetadata(repo);
+
}),
+
);
+
return metadata.filter((account) => account !== null) as AccountMetadata[];
+
};
+
+
const identityResolve = async (did: At.Did) => {
+
const resolver = new CompositeDidDocumentResolver({
+
methods: {
+
plc: new PlcDidDocumentResolver(),
+
web: new WebDidDocumentResolver(),
+
},
+
});
+
+
if (did.startsWith("did:plc:") || did.startsWith("did:web:")) {
+
const doc = await resolver.resolve(
+
did as `did:plc:${string}` | `did:web:${string}`,
+
);
+
return doc;
+
} else {
+
throw new Error(`Unsupported DID type: ${did}`);
+
}
+
};
+
+
const blueskyHandleFromDid = async (did: At.Did) => {
+
const doc = await identityResolve(did);
+
if (doc.alsoKnownAs) {
+
const handleAtUri = doc.alsoKnownAs.find((url) => url.startsWith("at://"));
+
const handle = handleAtUri?.split("/")[2];
+
if (!handle) {
+
return "Handle not found";
+
} else {
+
return handle;
+
}
+
} else {
+
return "Handle not found";
+
}
+
};
+
+
interface PostsAcc {
+
posts: ComAtprotoRepoListRecords.Record[];
+
account: AccountMetadata;
+
}
+
const getCutoffDate = (postAccounts: PostsAcc[]) => {
+
const now = Date.now();
+
let cutoffDate: Date | null = null;
+
postAccounts.forEach((postAcc) => {
+
const latestPost = new Date(
+
(postAcc.posts[postAcc.posts.length - 1].value as AppBskyFeedPost.Record)
+
.createdAt,
+
);
+
if (!cutoffDate) {
+
cutoffDate = latestPost;
+
} else {
+
if (latestPost > cutoffDate) {
+
cutoffDate = latestPost;
+
}
+
}
+
});
+
if (cutoffDate) {
+
return cutoffDate;
+
} else {
+
return new Date(now);
+
}
+
};
+
+
const filterPostsByDate = (posts: PostsAcc[], cutoffDate: Date) => {
+
// filter posts for each account that are older than the cutoff date and save the cursor of the last post included
+
const filteredPosts: PostsAcc[] = posts.map((postAcc) => {
+
const filtered = postAcc.posts.filter((post) => {
+
const postDate = new Date(
+
(post.value as AppBskyFeedPost.Record).createdAt,
+
);
+
return postDate >= cutoffDate;
+
});
+
if (filtered.length > 0) {
+
postAcc.account.currentCursor = processAtUri(filtered[filtered.length - 1].uri).rkey;
+
}
+
return {
+
posts: filtered,
+
account: postAcc.account,
+
};
+
});
+
return filteredPosts;
+
};
+
+
const postsMutex = new Mutex();
+
// nightmare function. However it works so I am not touching it
+
const getNextPosts = async () => {
+
const release = await postsMutex.obtain();
+
if (!accountsMetadata.length) {
+
accountsMetadata = await getAllMetadataFromPds();
+
}
+
+
const postsAcc: PostsAcc[] = await Promise.all(
+
accountsMetadata.map(async (account) => {
+
const posts = await fetchPostsForUser(
+
account.did,
+
account.currentCursor || null,
+
);
+
if (posts) {
+
return {
+
posts: posts,
+
account: account,
+
};
+
} else {
+
return {
+
posts: [],
+
account: account,
+
};
+
}
+
}),
+
);
+
const recordsFiltered = postsAcc.filter((postAcc) =>
+
postAcc.posts.length > 0
+
);
+
const cutoffDate = getCutoffDate(recordsFiltered);
+
const recordsCutoff = filterPostsByDate(recordsFiltered, cutoffDate);
+
// update the accountMetadata with the new cursor
+
accountsMetadata = accountsMetadata.map((account) => {
+
const postAcc = recordsCutoff.find(
+
(postAcc) => postAcc.account.did == account.did,
+
);
+
if (postAcc) {
+
account.currentCursor = postAcc.account.currentCursor;
+
}
+
return account;
+
}
+
);
+
// throw the records in a big single array
+
let records = recordsCutoff.flatMap((postAcc) => postAcc.posts);
+
// sort the records by timestamp
+
records = records.sort((a, b) => {
+
const aDate = new Date(
+
(a.value as AppBskyFeedPost.Record).createdAt,
+
).getTime();
+
const bDate = new Date(
+
(b.value as AppBskyFeedPost.Record).createdAt,
+
).getTime();
+
return bDate - aDate;
+
});
+
// filter out posts that are in the future
+
if (!Config.SHOW_FUTURE_POSTS) {
+
const now = Date.now();
+
records = records.filter((post) => {
+
const postDate = new Date(
+
(post.value as AppBskyFeedPost.Record).createdAt,
+
).getTime();
+
return postDate <= now;
+
});
+
}
+
+
const newPosts = records.map((record) => {
+
const account = accountsMetadata.find(
+
(account) => account.did == processAtUri(record.uri).repo,
+
);
+
if (!account) {
+
throw new Error(
+
`Account with DID ${processAtUri(record.uri).repo} not found`,
+
);
+
}
+
return new Post(record, account);
+
});
+
// release the mutex
+
release();
+
return newPosts;
+
};
+
+
const fetchPostsForUser = async (did: At.Did, cursor: string | null) => {
+
try {
+
const { data } = await rpc.get("com.atproto.repo.listRecords", {
+
params: {
+
repo: did as At.Identifier,
+
collection: "app.bsky.feed.post",
+
limit: Config.MAX_POSTS,
+
cursor: cursor || undefined,
+
},
+
});
+
return data.records as ComAtprotoRepoListRecords.Record[];
+
} catch (e) {
+
console.error(`Error fetching posts for ${did}:`, e);
+
return null;
+
}
+
};
+
+
export { getAllMetadataFromPds, getNextPosts, Post };
+
export type { AccountMetadata };
+9
src/main.ts
···
···
+
import { mount } from "svelte";
+
import "./app.css";
+
import App from "./App.svelte";
+
+
const app = mount(App, {
+
target: document.getElementById("app")!,
+
});
+
+
export default app;
+2
src/vite-env.d.ts
···
···
+
/// <reference types="svelte" />
+
/// <reference types="vite/client" />
+7
svelte.config.js
···
···
+
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
+
+
export default {
+
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
+
// for more information about preprocessors
+
preprocess: vitePreprocess(),
+
};
+432
themes/birdrights/theme.css
···
···
+
@font-face {
+
font-family: "Recursive Variable";
+
font-weight: 100 700;
+
font-style: -5deg 5deg;
+
src: url(/fonts/Recursive_VF_1.085.woff2) format('woff2');
+
}
+
+
:root {
+
/* Color overrides, edit to whatever you want */
+
--primary-h: 260; /* Hue */
+
--background: #EFD7E4;
+
--foreground: #333333;
+
+
--link-color: color-mix(in srgb-linear, color-mix(in srgb-linear, var(--background), red 50%), black 25%);
+
--link-hover-color: color-mix(in srgb-linear, var(--link-color), black 25%);
+
--background-color: var(--background);
+
--header-background-color: var(--background);
+
--content-background-color: rgba(255,255,255,.5);
+
--text-color: var(--foreground);
+
--border-color: color-mix(in srgb-linear, var(--text-color), rgba(0,0,0,.15));
+
--indicator-inactive-color: var(--link-hover-color);
+
--indicator-active-color: var(--link-color);
+
--base-font-size: 14px;
+
}
+
+
* {
+
box-sizing: border-box;
+
}
+
+
body {
+
margin: 20px;
+
display: flex;
+
place-items: center;
+
min-width: 320px;
+
min-height: 100vh;
+
background-color: var(--background-color);
+
font-family: "Recursive Variable";
+
font-size: var(--base-font-size);
+
color: var(--text-color);
+
border-color: var(--border-color);
+
overflow-wrap: break-word;
+
word-wrap: normal;
+
word-break: break-word;
+
hyphens: none;
+
}
+
+
a {
+
font-weight: 500;
+
color: var(--link-color);
+
text-decoration: inherit;
+
}
+
a:hover {
+
color: var(--link-hover-color);
+
text-decoration: underline;
+
}
+
+
h1 {
+
font-size: 2em;
+
line-height: 1.1;
+
margin-bottom: 0;
+
text-align: center;
+
}
+
+
#app {
+
max-width: 600px;
+
width: 100%;
+
margin: 0;
+
padding: 0;
+
margin-left: auto;
+
margin-right: auto;
+
text-align: center;
+
}
+
+
/* Page Structure */
+
#content {
+
/* split the screen in half, left for accounts, right for posts */
+
width: 100%;
+
margin: 0 auto;
+
}
+
+
#account {
+
text-align: center;
+
margin-bottom: 20px;
+
}
+
+
#accountsList {
+
display: grid;
+
grid-template-columns: repeat(12, 1fr);
+
gap: 10px;
+
overflow-y: scroll;
+
height: 100%;
+
width: 100%;
+
padding: 0px;
+
margin: 0px;
+
}
+
+
#feed {
+
width: 100%;
+
margin-top: 0;
+
margin-bottom: 0;
+
}
+
+
/* Header */
+
#subtitle {
+
font-size: 1.1rem;
+
font-style: oblique 5deg;
+
margin: 0;
+
}
+
+
#asciiart {
+
font-size: .8rem;
+
line-height: 1;
+
margin: 0 auto;
+
display: inline-block;
+
}
+
+
/* Post Component */
+
.postContainer {
+
border: 1px solid var(--border-color);
+
background-color: var(--background-color);
+
margin-bottom: 15px;
+
overflow-wrap: break-word;
+
overflow: hidden;
+
border-radius: 5px 5px 3px 3px;
+
}
+
+
.postHeader {
+
display: flex;
+
flex-direction: row;
+
align-items: center;
+
justify-content: start;
+
background-color: var(--header-background-color);
+
padding: 0px 0px;
+
height: fit-content;
+
border-bottom: 1px solid var(--border-color);
+
font-weight: bold;
+
overflow-wrap: break-word;
+
height: 60px;
+
}
+
+
.headerText {
+
margin-left: 10px;
+
font-size: 0.9em;
+
text-align: start;
+
word-break: break-word;
+
max-width: 80%;
+
max-height: 95%;
+
overflow: hidden;
+
align-self: flex-start;
+
margin-top: auto;
+
margin-bottom: auto;
+
}
+
+
.displayName {
+
display: block;
+
color: var(--text-color);
+
font-size: 1.2em;
+
padding: 0;
+
margin: 0;
+
overflow-wrap: normal;
+
word-wrap: break-word;
+
word-break: break-word;
+
text-overflow: ellipsis;
+
overflow: hidden;
+
white-space: nowrap;
+
width: 100%;
+
}
+
+
.handle {
+
display: block;
+
color: var(--border-color);
+
font-size: 0.8em;
+
padding: 0;
+
margin: 0;
+
}
+
+
.avatar {
+
height: 60px;
+
width: 60px;
+
margin: 0px;
+
margin-left: 0px;
+
overflow: hidden;
+
object-fit: cover;
+
border-right: var(--border-color) 1px solid;
+
}
+
+
.postLink {
+
color: var(--border-color);
+
font-size: 0.8em;
+
padding: 0;
+
margin: 0;
+
}
+
+
.postContent {
+
display: flex;
+
text-align: start;
+
flex-direction: column;
+
padding: 10px;
+
background-color: var(--content-background-color);
+
color: var(--text-color);
+
overflow-wrap: break-word;
+
white-space: pre-line;
+
}
+
+
.replyingText {
+
font-size: 0.7em;
+
margin: 0;
+
padding: 0;
+
padding-bottom: 5px;
+
}
+
+
.quotingText {
+
font-size: 0.7em;
+
margin: 0;
+
padding: 0;
+
padding-bottom: 5px;
+
}
+
+
.postText {
+
margin: 0;
+
padding: 0;
+
overflow-wrap: break-word;
+
word-wrap: normal;
+
word-break: break-word;
+
hyphens: none;
+
}
+
+
.carouselContainer {
+
position: relative;
+
width: 100%;
+
margin-top: 10px;
+
display: flex;
+
flex-direction: column;
+
align-items: center;
+
}
+
+
.carouselControls {
+
display: flex;
+
justify-content: space-between;
+
align-items: center;
+
width: 100%;
+
max-width: 500px;
+
margin-top: 5px;
+
}
+
+
.carouselIndicators {
+
display: flex;
+
gap: 5px;
+
}
+
+
.indicator {
+
width: 8px;
+
height: 8px;
+
background-color: var(--indicator-inactive-color);
+
}
+
+
.indicator.active {
+
background-color: var(--indicator-active-color);
+
}
+
+
.prevBtn,
+
.nextBtn {
+
background-color: rgba(31, 17, 69, 0.7);
+
color: var(--text-color);
+
border: 1px solid var(--border-color);
+
width: 30px;
+
height: 30px;
+
cursor: pointer;
+
display: flex;
+
align-items: center;
+
justify-content: center;
+
}
+
+
.prevBtn:disabled,
+
.nextBtn:disabled {
+
opacity: 0.5;
+
cursor: not-allowed;
+
}
+
+
.embedVideo {
+
width: 100%;
+
max-width: 500px;
+
margin-top: 10px;
+
align-self: center;
+
}
+
+
.embedImages {
+
min-width: min(100%, 500px);
+
max-width: min(100%, 500px);
+
max-height: 500px;
+
object-fit: contain;
+
+
margin: 0;
+
}
+
+
/* Account Component */
+
.accountBtn {
+
appearance: none;
+
border: none;
+
background-color: transparent;
+
padding: 0;
+
position: relative;
+
}
+
+
.accountContainer {
+
padding: 0px;
+
border: 1px solid var(--border-color);
+
min-height: 30px;
+
overflow: hidden;
+
border-radius: 3px;
+
}
+
+
.accountContainer img.avatar {
+
width: 100%;
+
height: auto;
+
border-right: none;
+
display: block;
+
}
+
+
.accountName {
+
margin-left: 10px;
+
font-size: 1em;
+
max-width: 80%;
+
overflow: hidden;
+
text-overflow: ellipsis;
+
white-space: nowrap;
+
}
+
+
.accountTooltip {
+
border: 1px var(--border-color) solid;
+
color: var(--text-color);
+
background-color: var(--content-background-color);
+
backdrop-filter: blur(5px);
+
border-radius: 3px;
+
padding: 10px;
+
text-align: left;
+
font-size: 13px;
+
width: 300px;
+
}
+
+
.banner {
+
position: relative;
+
margin-bottom: 10px;
+
width: calc(100% + 20px);
+
margin-left: -10px;
+
margin-right: -10px;
+
margin-top: -10px;
+
}
+
+
.banner img.bannerImg {
+
width: 100%;
+
height: 100px;
+
object-fit: cover;
+
display: block;
+
border-bottom: 1px var(--border-color) solid;
+
}
+
+
.banner .avatarInsetImg {
+
position: absolute;
+
left: 10px;
+
top: 60px;
+
border-radius: 3px;
+
border: 1px var(--border-color) solid;
+
}
+
+
.accountTooltip .displayName {
+
font-weight: bold;
+
}
+
+
.no-avatar {
+
+
}
+
+
/* Source Link */
+
+
#footer {
+
position: absolute;
+
bottom: 5px;
+
right: 5px;
+
z-index: 99;
+
opacity: .8;
+
transition: 0.3s;
+
font-size: .8em;
+
text-transform: lowercase;
+
background-color: rgba(255,255,255,.5);
+
padding: 2px 5px;
+
border-radius: 10px;
+
}
+
+
#footer:hover {
+
opacity: 1;
+
}
+
+
/* Responsive Styling */
+
@media screen and (max-width: 600px) {
+
#accountsList {
+
grid-template-columns: repeat(6, 1fr);
+
}
+
}
+
+
/* Scrollbars */
+
+
::-webkit-scrollbar {
+
width: 0px;
+
background: transparent;
+
padding: 0;
+
margin: 0;
+
}
+
::-webkit-scrollbar-thumb {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-track {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-corner {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-button {
+
background: transparent;
+
border-radius: 0;
+
}
+
+
* {
+
scrollbar-width: none;
+
scrollbar-color: transparent transparent;
+
-ms-overflow-style: none; /* IE and Edge */
+
-webkit-overflow-scrolling: touch;
+
-webkit-scrollbar: none; /* Safari */
+
}
+423
themes/default/theme.css
···
···
+
/* Modern Theme for pds-dash */
+
+
:root {
+
/* Modern color palette */
+
--primary-h: 243;
+
--link-color: hsl(var(--primary-h), 73%, 59%);
+
--link-hover-color: #4338ca;
+
--time-color: #8b5cf6;
+
--background-color: #f8fafc;
+
--header-background-color: #ffffff;
+
--content-background-color: #ffffff;
+
--text-color: #111827;
+
--text-secondary-color: #4b5563;
+
--border-color: #e2e8f0;
+
--indicator-inactive-color: #cbd5e1;
+
--indicator-active-color: #6366f1;
+
+
/* Modern shadows */
+
--button-hover: #f3f4f6;
+
}
+
+
+
body {
+
margin: 0;
+
display: flex;
+
place-items: center;
+
min-width: 320px;
+
min-height: 100vh;
+
background-color: var(--background-color);
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
+
font-size: 18px;
+
line-height: 1.5;
+
color: var(--text-color);
+
border-color: var(--border-color);
+
overflow-wrap: break-word;
+
word-break: break-word;
+
hyphens: none;
+
}
+
+
a {
+
font-weight: 500;
+
color: var(--link-color);
+
text-decoration: none;
+
transition: color 0.15s ease;
+
}
+
a:hover {
+
color: var(--link-hover-color);
+
}
+
+
h1 {
+
font-size: 2.5em;
+
line-height: 1.2;
+
font-weight: 700;
+
}
+
+
#app {
+
max-width: 1400px;
+
width: 100%;
+
margin: 0 auto;
+
padding: 0;
+
text-align: center;
+
}
+
+
/* Post Component */
+
#postContainer {
+
display: flex;
+
flex-direction: column;
+
border-radius: 12px;
+
border: 1px solid var(--border-color);
+
background-color: var(--content-background-color);
+
margin-bottom: 20px;
+
overflow-wrap: break-word;
+
overflow: hidden;
+
box-shadow: var(--card-shadow);
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
+
}
+
+
#postContainer:hover {
+
transform: translateY(-2px);
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
}
+
+
#postHeader {
+
display: flex;
+
flex-direction: row;
+
align-items: center;
+
justify-content: start;
+
background-color: var(--header-background-color);
+
padding: 12px 16px;
+
height: 60px;
+
border-bottom: 1px solid var(--border-color);
+
font-weight: 600;
+
overflow-wrap: break-word;
+
}
+
+
#displayName {
+
display: block;
+
color: var(--text-color);
+
font-size: 1.1em;
+
padding: 0;
+
margin: 0 0 2px 0;
+
text-overflow: ellipsis;
+
overflow: hidden;
+
white-space: nowrap;
+
width: 100%;
+
letter-spacing: -0.01em;
+
}
+
+
#handle {
+
display: flex;
+
align-items: center;
+
color: #6b7280;
+
font-size: 0.85em;
+
font-weight: 400;
+
padding: 0;
+
margin: 0;
+
gap: 8px;
+
}
+
+
#postLink {
+
color: var(--time-color);
+
font-size: 0.85em;
+
padding: 0;
+
margin: 0;
+
opacity: 0.9;
+
}
+
+
#postContent {
+
display: flex;
+
text-align: start;
+
flex-direction: column;
+
padding: 16px;
+
background-color: var(--content-background-color);
+
color: var(--text-color);
+
overflow-wrap: break-word;
+
white-space: pre-line;
+
line-height: 1.6;
+
}
+
+
#replyingText, #quotingText {
+
font-size: 0.8em;
+
margin: 0;
+
padding: 0 0 10px 0;
+
color: #6b7280;
+
}
+
+
#postText {
+
margin: 0 0 8px 0;
+
padding: 0;
+
overflow-wrap: break-word;
+
word-break: break-word;
+
hyphens: none;
+
font-size: 1.05em;
+
}
+
+
#headerText {
+
margin-left: 12px;
+
font-size: 0.9em;
+
text-align: start;
+
word-break: break-word;
+
max-width: 80%;
+
max-height: 95%;
+
overflow: hidden;
+
align-self: flex-start;
+
margin-top: auto;
+
margin-bottom: auto;
+
}
+
+
#carouselContainer {
+
position: relative;
+
width: 100%;
+
margin-top: 12px;
+
display: flex;
+
flex-direction: column;
+
align-items: center;
+
border-radius: 8px;
+
overflow: hidden;
+
}
+
+
#carouselControls {
+
display: flex;
+
justify-content: space-between;
+
align-items: center;
+
width: 100%;
+
max-width: 500px;
+
margin-top: 10px;
+
}
+
+
#carouselIndicators {
+
display: flex;
+
gap: 6px;
+
}
+
+
.indicator {
+
width: 6px;
+
height: 6px;
+
background-color: var(--indicator-inactive-color);
+
border-radius: 50%;
+
transition: background-color 0.2s ease, transform 0.2s ease;
+
}
+
+
.indicator.active {
+
background-color: var(--indicator-active-color);
+
transform: scale(1.3);
+
}
+
+
#prevBtn,
+
#nextBtn {
+
background-color: var(--button-bg);
+
color: var(--text-color);
+
border: 1px solid var(--border-color);
+
width: 32px;
+
height: 32px;
+
cursor: pointer;
+
display: flex;
+
align-items: center;
+
justify-content: center;
+
border-radius: 50%;
+
transition: background-color 0.15s ease, transform 0.15s ease;
+
font-size: 16px;
+
}
+
+
#prevBtn:hover:not(:disabled),
+
#nextBtn:hover:not(:disabled) {
+
background-color: var(--button-hover);
+
transform: scale(1.05);
+
}
+
+
#prevBtn:disabled,
+
#nextBtn:disabled {
+
opacity: 0.4;
+
cursor: not-allowed;
+
}
+
+
#embedVideo {
+
width: 100%;
+
max-width: 500px;
+
margin-top: 12px;
+
align-self: center;
+
border-radius: 8px;
+
overflow: hidden;
+
}
+
+
#embedImages {
+
min-width: min(100%, 500px);
+
max-width: min(100%, 500px);
+
max-height: 500px;
+
object-fit: contain;
+
margin: 0;
+
border-radius: 8px;
+
}
+
+
/* Account Component */
+
#accountContainer {
+
display: flex;
+
text-align: start;
+
align-items: center;
+
background-color: var(--content-background-color);
+
padding: 12px;
+
margin-bottom: 15px;
+
border: 1px solid var(--border-color);
+
border-radius: 12px;
+
transition: background-color 0.15s ease;
+
}
+
+
#accountContainer:hover {
+
background-color: var(--hover-bg);
+
}
+
+
#accountName {
+
margin-left: 12px;
+
font-size: 0.95em;
+
max-width: 80%;
+
overflow: hidden;
+
text-overflow: ellipsis;
+
white-space: nowrap;
+
font-weight: 500;
+
}
+
+
#avatar {
+
width: 48px;
+
height: 48px;
+
margin: 0;
+
object-fit: cover;
+
border-radius: 50%;
+
border: 2px solid white;
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+
}
+
+
/* App.Svelte Layout */
+
#Content {
+
display: flex;
+
width: 100%;
+
height: 100%;
+
flex-direction: row;
+
justify-content: space-between;
+
align-items: center;
+
background-color: var(--background-color);
+
color: var(--text-color);
+
gap: 24px;
+
}
+
+
#Feed {
+
overflow-y: auto;
+
width: 65%;
+
height: 100vh;
+
padding-right: 16px;
+
align-self: flex-start;
+
}
+
+
#spacer {
+
padding: 0;
+
margin: 0;
+
height: 10vh;
+
width: 100%;
+
}
+
+
#Account {
+
width: 35%;
+
display: flex;
+
flex-direction: column;
+
border: 1px solid var(--border-color);
+
background-color: var(--content-background-color);
+
max-height: 80vh;
+
padding: 24px;
+
margin-left: 16px;
+
border-radius: 12px;
+
box-shadow: var(--card-shadow);
+
}
+
+
#accountsList {
+
display: flex;
+
flex-direction: column;
+
overflow-y: auto;
+
height: 100%;
+
width: 100%;
+
padding: 8px 0;
+
margin: 0;
+
}
+
+
#Header {
+
text-align: center;
+
font-size: 1.8em;
+
margin-bottom: 16px;
+
font-weight: 700;
+
background: linear-gradient(to right, var(--link-color), #8b5cf6);
+
-webkit-background-clip: text;
+
-webkit-text-fill-color: transparent;
+
background-clip: text;
+
}
+
+
/* Mobile Styles */
+
@media screen and (max-width: 768px) {
+
#Content {
+
flex-direction: column;
+
width: auto;
+
padding: 12px;
+
margin-top: 0;
+
}
+
+
#Account {
+
width: calc(100% - 32px);
+
padding: 16px;
+
margin-bottom: 20px;
+
margin-left: 0;
+
margin-right: 0;
+
height: auto;
+
order: -1;
+
}
+
+
#Feed {
+
width: 100%;
+
margin: 0;
+
padding: 0;
+
overflow-y: visible;
+
}
+
+
#spacer {
+
height: 5vh;
+
}
+
+
body {
+
font-size: 16px;
+
}
+
+
#postHeader {
+
padding: 10px;
+
height: auto;
+
min-height: 50px;
+
}
+
}
+
+
/* Scrollbar Styles */
+
::-webkit-scrollbar {
+
width: 0px;
+
background: transparent;
+
padding: 0;
+
margin: 0;
+
}
+
::-webkit-scrollbar-thumb {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-track {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-corner {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-button {
+
background: transparent;
+
border-radius: 0;
+
}
+
+
* {
+
scrollbar-width: none;
+
scrollbar-color: transparent transparent;
+
-ms-overflow-style: none; /* IE and Edge */
+
-webkit-overflow-scrolling: touch;
+
-webkit-scrollbar: none; /* Safari */
+
}
+375
themes/express/theme.css
···
···
+
@import url("https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap");
+
+
:root {
+
/* Color overrides, edit to whatever you want */
+
--primary-h: 341; /* Hue */
+
--background-color: hsl(var(--primary-h), 62%, 30%);
+
--text-color: hsl(var(--primary-h), 69%, 18%);
+
--link-color: hsl(var(--primary-h), 100%, 20%);
+
--link-hover-color: hsl(var(--primary-h), 20%, 20%);
+
--border-color: hsl(var(--primary-h), 59%, 52%);
+
--content-background-color: hsl(var(--primary-h), 97%, 73%);
+
+
--header-background-color: hsl(var(--primary-h), 97%, 73%);
+
--indicator-inactive-color: #4a4a4a;
+
--indicator-active-color: var(--border-color);
+
}
+
+
a {
+
font-weight: 500;
+
color: var(--link-color);
+
text-decoration: inherit;
+
}
+
a:hover {
+
color: var(--link-hover-color);
+
text-decoration: underline;
+
}
+
+
body {
+
margin: 0;
+
display: flex;
+
place-items: center;
+
min-width: 320px;
+
min-height: 100vh;
+
background-color: var(--background-color);
+
font-family: "Share Tech Mono", monospace;
+
font-size: 24px;
+
color: var(--text-color);
+
border-color: var(--border-color);
+
overflow-wrap: break-word;
+
word-wrap: normal;
+
word-break: break-word;
+
hyphens: none;
+
}
+
+
h1 {
+
font-size: 3.2em;
+
line-height: 1.1;
+
}
+
+
#app {
+
max-width: 1400px;
+
width: 100%;
+
margin: 0;
+
padding: 0;
+
margin-left: auto;
+
margin-right: auto;
+
text-align: center;
+
}
+
+
/* Post Component */
+
a:hover {
+
text-decoration: underline;
+
}
+
#postContainer {
+
display: flex;
+
flex-direction: column;
+
border: 4px solid var(--border-color);
+
background-color: var(--background-color);
+
margin-bottom: 15px;
+
overflow-wrap: break-word;
+
box-shadow: var(--border-color) 10px 10px;
+
}
+
#postHeader {
+
display: flex;
+
flex-direction: row;
+
align-items: center;
+
justify-content: start;
+
background-color: var(--header-background-color);
+
padding: 0px 0px;
+
height: fit-content;
+
+
font-weight: bold;
+
overflow-wrap: break-word;
+
height: 64px;
+
}
+
#displayName {
+
display: block;
+
color: var(--text-color);
+
font-size: 1.2em;
+
padding: 0;
+
margin: 0;
+
overflow-wrap: normal;
+
word-wrap: break-word;
+
word-break: break-word;
+
text-overflow: ellipsis;
+
overflow: hidden;
+
white-space: nowrap;
+
width: 100%;
+
}
+
#handle {
+
display: block;
+
color: var(--border-color);
+
font-size: 0.8em;
+
padding: 0;
+
margin: 0;
+
}
+
+
#postLink {
+
color: var(--link-hover-color);
+
font-size: 0.8em;
+
padding: 0;
+
margin: 0;
+
}
+
#postContent {
+
display: flex;
+
text-align: start;
+
flex-direction: column;
+
padding: 10px;
+
background-color: var(--content-background-color);
+
color: var(--text-color);
+
overflow-wrap: break-word;
+
white-space: pre-line;
+
}
+
#replyingText {
+
font-size: 0.7em;
+
margin: 0;
+
padding: 0;
+
padding-bottom: 5px;
+
}
+
#quotingText {
+
font-size: 0.7em;
+
margin: 0;
+
padding: 0;
+
padding-bottom: 5px;
+
}
+
#postText {
+
margin: 0;
+
padding: 0;
+
overflow-wrap: break-word;
+
word-wrap: normal;
+
word-break: break-word;
+
hyphens: none;
+
}
+
#headerText {
+
margin-left: 10px;
+
font-size: 0.9em;
+
text-align: start;
+
word-break: break-word;
+
max-width: 80%;
+
max-height: 95%;
+
overflow: hidden;
+
align-self: flex-start;
+
margin-top: auto;
+
margin-bottom: auto;
+
}
+
#avatar {
+
height: 30px;
+
width: 30px;
+
overflow: hidden;
+
object-fit: cover;
+
}
+
#postContainer #avatar {
+
height: 60px;
+
width: 60px;
+
border-right: var(--border-color) 4px solid;
+
border-bottom: var(--border-color) 4px solid;
+
}
+
#carouselContainer {
+
position: relative;
+
width: 100%;
+
margin-top: 10px;
+
display: flex;
+
flex-direction: column;
+
align-items: center;
+
}
+
#carouselControls {
+
display: flex;
+
justify-content: space-between;
+
align-items: center;
+
width: 100%;
+
max-width: 500px;
+
margin-top: 5px;
+
}
+
#carouselIndicators {
+
display: flex;
+
gap: 5px;
+
}
+
.indicator {
+
width: 8px;
+
height: 8px;
+
background-color: var(--indicator-inactive-color);
+
}
+
.indicator.active {
+
background-color: var(--indicator-active-color);
+
}
+
#prevBtn,
+
#nextBtn {
+
background-color: rgba(31, 17, 69, 0.7);
+
color: var(--text-color);
+
border: 4px solid var(--border-color);
+
width: 30px;
+
height: 30px;
+
cursor: pointer;
+
display: flex;
+
align-items: center;
+
justify-content: center;
+
}
+
#prevBtn:disabled,
+
#nextBtn:disabled {
+
opacity: 0.5;
+
cursor: not-allowed;
+
}
+
#embedVideo {
+
width: 100%;
+
max-width: 500px;
+
margin-top: 10px;
+
align-self: center;
+
}
+
+
#embedImages {
+
min-width: min(100%, 500px);
+
max-width: min(100%, 500px);
+
max-height: 500px;
+
object-fit: contain;
+
+
margin: 0;
+
}
+
+
/* Account Component */
+
#accountContainer {
+
display: flex;
+
text-align: start;
+
align-items: center;
+
background-color: var(--header-background-color);
+
padding: 0px;
+
margin-bottom: 15px;
+
margin-right: 4px;
+
border: 4px solid var(--border-color);
+
box-shadow: var(--border-color) 10px 10px;
+
min-height: 30px;
+
}
+
#accountName {
+
margin-left: 10px;
+
font-size: 1em;
+
max-width: 80%;
+
+
/* replace overflow with ellipsis */
+
overflow: hidden;
+
text-overflow: ellipsis;
+
white-space: nowrap;
+
}
+
+
.no-avatar {
+
margin-left: 40px !important;
+
}
+
+
/* App.Svelte */
+
/* desktop style */
+
+
#Content {
+
display: flex;
+
/* split the screen in half, left for accounts, right for posts */
+
width: 100%;
+
height: 100%;
+
flex-direction: row;
+
justify-content: space-between;
+
align-items: center;
+
background-color: var(--background-color);
+
color: var(--text-color);
+
}
+
#Feed {
+
overflow-y: scroll;
+
width: 65%;
+
height: 100vh;
+
padding: 20px;
+
padding-bottom: 0;
+
padding-top: 0;
+
margin-top: 0;
+
margin-bottom: 0;
+
}
+
#spacer {
+
padding: 0;
+
margin: 0;
+
height: 10vh;
+
width: 100%;
+
}
+
#Account {
+
width: 35%;
+
display: flex;
+
flex-direction: column;
+
border: 4px solid var(--border-color);
+
background-color: var(--content-background-color);
+
box-shadow: var(--border-color) 10px 10px;
+
height: 80vh;
+
padding: 20px;
+
margin-left: 20px;
+
}
+
#accountsList {
+
display: flex;
+
flex-direction: column;
+
overflow-y: scroll;
+
height: 100%;
+
width: 100%;
+
padding: 0px;
+
margin: 0px;
+
}
+
+
#Header {
+
text-align: center;
+
font-size: 2em;
+
margin-bottom: 20px;
+
}
+
+
/* mobile style */
+
@media screen and (max-width: 600px) {
+
#Content {
+
flex-direction: column;
+
width: auto;
+
padding-left: 0px;
+
padding-right: 0px;
+
margin-top: 5%;
+
}
+
#Account {
+
width: 85%;
+
padding-left: 5%;
+
padding-right: 5%;
+
margin-bottom: 20px;
+
margin-left: 5%;
+
margin-right: 5%;
+
height: auto;
+
}
+
#Feed {
+
width: 95%;
+
margin: 0px;
+
margin-left: 10%;
+
margin-right: 10%;
+
padding: 0px;
+
overflow-y: visible;
+
}
+
+
#spacer {
+
height: 0;
+
}
+
}
+
+
::-webkit-scrollbar {
+
width: 0px;
+
background: transparent;
+
padding: 0;
+
margin: 0;
+
}
+
::-webkit-scrollbar-thumb {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-track {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-corner {
+
background: transparent;
+
border-radius: 0;
+
}
+
::-webkit-scrollbar-button {
+
background: transparent;
+
border-radius: 0;
+
}
+
+
* {
+
scrollbar-width: none;
+
scrollbar-color: transparent transparent;
+
-ms-overflow-style: none; /* IE and Edge */
+
-webkit-overflow-scrolling: touch;
+
-webkit-scrollbar: none; /* Safari */
+
}
+32
theming.ts
···
···
+
import { Plugin } from 'vite';
+
import { Config } from './config';
+
+
+
// Replaces app.css with the contents of the file specified in the
+
// config file.
+
export const themePlugin = (): Plugin => {
+
const themeFolder = Config.THEME;
+
console.log(`Using theme folder: ${themeFolder}`);
+
return {
+
name: 'theme-generator',
+
enforce: 'pre', // Ensure this plugin runs first
+
transform(code, id) {
+
if (id.endsWith('app.css')) {
+
// Read the theme file and replace the contents of app.css with it
+
// Needs full path to the file
+
const themeCode = Deno.readTextFileSync(Deno.cwd() + '/themes/' + themeFolder + '/theme.css');
+
// Replace the contents of app.css with the theme code
+
+
// and add a comment at the top
+
const themeComment = `/* Generated from ${themeFolder} */\n`;
+
const themeCodeWithComment = themeComment + themeCode;
+
// Return the theme code as the new contents of app.css
+
return {
+
code: themeCodeWithComment,
+
map: null,
+
};
+
}
+
return null;
+
}
+
};
+
};
+20
tsconfig.app.json
···
···
+
{
+
"extends": "@tsconfig/svelte/tsconfig.json",
+
"compilerOptions": {
+
"target": "ESNext",
+
"useDefineForClassFields": true,
+
"module": "ESNext",
+
"resolveJsonModule": true,
+
/**
+
* Typecheck JS in `.svelte` and `.js` files by default.
+
* Disable checkJs if you'd like to use dynamic types in JS.
+
* Note that setting allowJs false does not prevent the use
+
* of JS in `.svelte` files.
+
*/
+
"allowJs": true,
+
"checkJs": true,
+
"isolatedModules": true,
+
"moduleDetection": "force"
+
},
+
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
+
}
+7
tsconfig.json
···
···
+
{
+
"files": [],
+
"references": [
+
{ "path": "./tsconfig.app.json" },
+
{ "path": "./tsconfig.node.json" }
+
]
+
}
+24
tsconfig.node.json
···
···
+
{
+
"compilerOptions": {
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+
"target": "ES2022",
+
"lib": ["ES2023"],
+
"module": "ESNext",
+
"skipLibCheck": true,
+
+
/* Bundler mode */
+
"moduleResolution": "bundler",
+
"allowImportingTsExtensions": true,
+
"isolatedModules": true,
+
"moduleDetection": "force",
+
"noEmit": true,
+
+
/* Linting */
+
"strict": true,
+
"noUnusedLocals": true,
+
"noUnusedParameters": true,
+
"noFallthroughCasesInSwitch": true,
+
"noUncheckedSideEffectImports": true
+
},
+
"include": ["vite.config.ts"]
+
}
+11
vite.config.ts
···
···
+
import { defineConfig } from "vite";
+
import { svelte } from "@sveltejs/vite-plugin-svelte";
+
import { themePlugin } from "./theming";
+
+
// https://vite.dev/config/
+
export default defineConfig({
+
plugins: [
+
themePlugin(),
+
svelte(),
+
],
+
});