tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
19
fork
atom
overview
issues
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
various-fixes
updated-blentos
update-docs
timer-card-tiny-fix
theme-colors
switch-map
switch-grid-layout
statusphere-fix
small-fixes
signup
show-login-error
section-settings
section-fix-undo
remove-extra-buttons
refactor-cards
record-visualizer-card
qr-codes
profile-stuff-2
profile-stuff
product-hunt
polijn/main
pages
npmx
next
new-og-image-wip
move-qr-click
mobile-editing
map
main-favicon
main
mail-icon
lastfm
kickstarter-card
invalid-handle-fix
improve-saving
improve-oauth
improve-link-card
improve-fluid-text
image-fixes
hide-friends
github-contribs
gifs-heypster
funding
fuck-another-fix
floating-button
fixes
fix-xss
fix-timer-stuff
fix-signup-pds
fix-package-manager
fix-invalid-site.standard.documents
fix-formatting
fix-favicon
fix-build
event-card
edit-profile
drawing-card
custom-domains-editing
custom-domains
copy-page
card-label
card-command-bar-v2
card-command-bar
button
bluesky-post-nsfw-labels
bluesky-post-card
bluesky-feed-card
apple-music-playlist
no tags found
compare:
various-fixes
updated-blentos
update-docs
timer-card-tiny-fix
theme-colors
switch-map
switch-grid-layout
statusphere-fix
small-fixes
signup
show-login-error
section-settings
section-fix-undo
remove-extra-buttons
refactor-cards
record-visualizer-card
qr-codes
profile-stuff-2
profile-stuff
product-hunt
polijn/main
pages
npmx
next
new-og-image-wip
move-qr-click
mobile-editing
map
main-favicon
main
mail-icon
lastfm
kickstarter-card
invalid-handle-fix
improve-saving
improve-oauth
improve-link-card
improve-fluid-text
image-fixes
hide-friends
github-contribs
gifs-heypster
funding
fuck-another-fix
floating-button
fixes
fix-xss
fix-timer-stuff
fix-signup-pds
fix-package-manager
fix-invalid-site.standard.documents
fix-formatting
fix-favicon
fix-build
event-card
edit-profile
drawing-card
custom-domains-editing
custom-domains
copy-page
card-label
card-command-bar-v2
card-command-bar
button
bluesky-post-nsfw-labels
bluesky-post-card
bluesky-feed-card
apple-music-playlist
no tags found
go
+451
-2
7 changed files
expand all
collapse all
unified
split
.gitignore
package.json
pnpm-lock.yaml
src
lib
cards
index.ts
visual
RecordVisualizerCard
RecordVisualizerCard.svelte
RecordVisualizerSettings.svelte
index.ts
+1
-1
.gitignore
···
22
vite.config.js.timestamp-*
23
vite.config.ts.timestamp-*
24
25
-
react-grid-layout
···
22
vite.config.js.timestamp-*
23
vite.config.ts.timestamp-*
24
25
+
references
+1
package.json
···
79
"mapbox-gl": "^3.18.1",
80
"marked": "^17.0.1",
81
"perfect-freehand": "^1.2.2",
0
82
"plyr": "^3.8.4",
83
"qr-code-styling": "^1.8.6",
84
"react-grid-layout": "^2.2.2",
···
79
"mapbox-gl": "^3.18.1",
80
"marked": "^17.0.1",
81
"perfect-freehand": "^1.2.2",
82
+
"pixi.js": "^8.16.0",
83
"plyr": "^3.8.4",
84
"qr-code-styling": "^1.8.6",
85
"react-grid-layout": "^2.2.2",
+69
-1
pnpm-lock.yaml
···
128
perfect-freehand:
129
specifier: ^1.2.2
130
version: 1.2.2
0
0
0
131
plyr:
132
specifier: ^3.8.4
133
version: 3.8.4
···
978
peerDependencies:
979
svelte: ^4 || ^5
980
0
0
0
981
'@polka/url@1.0.0-next.29':
982
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
983
···
1508
'@types/cookie@0.6.0':
1509
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
1510
0
0
0
1511
'@types/estree@1.0.8':
1512
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
1513
···
1624
'@webgpu/types@0.1.69':
1625
resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==}
1626
0
0
0
0
1627
acorn-jsx@5.3.2:
1628
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
1629
peerDependencies:
···
1853
resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==}
1854
1855
earcut@3.0.2:
1856
-
resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==}
1857
1858
emoji-picker-element@1.28.1:
1859
resolution: {integrity: sha512-8c64IPish2PWoV9oYCo2pvuPHwIv+uK9bO0dfpPyMupDAvaWL9ZvYhWNTAR+2sx7BhfRjciImqP6CIUgNX+DMg==}
···
1964
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
1965
engines: {node: '>=0.10.0'}
1966
0
0
0
1967
exsolve@1.0.8:
1968
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
1969
···
2017
geojson-vt@4.0.2:
2018
resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==}
2019
0
0
0
2020
gl-matrix@3.4.4:
2021
resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==}
2022
···
2102
isexe@2.0.0:
2103
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
2104
0
0
0
2105
iso-datestring-validator@2.2.2:
2106
resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==}
2107
···
2109
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
2110
hasBin: true
2111
0
0
0
2112
js-tokens@4.0.0:
2113
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, tarball: https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz}
2114
···
2385
parse-css-color@0.2.1:
2386
resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==}
2387
0
0
0
2388
parse5-htmlparser2-tree-adapter@7.1.0:
2389
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
2390
···
2422
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
2423
engines: {node: '>=12'}
2424
0
0
0
2425
pkg-types@1.3.1:
2426
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
2427
···
2897
tiny-inflate@1.0.3:
2898
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
2899
0
0
0
0
2900
tinyglobby@0.2.15:
2901
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
2902
engines: {node: '>=12.0.0'}
···
3750
number-flow: 0.5.9
3751
svelte: 5.48.0
3752
0
0
3753
'@polka/url@1.0.0-next.29': {}
3754
3755
'@poppinss/colors@4.1.6':
···
4224
4225
'@types/cookie@0.6.0': {}
4226
0
0
4227
'@types/estree@1.0.8': {}
4228
4229
'@types/geojson-vt@3.2.5':
···
4373
4374
'@webgpu/types@0.1.69': {}
4375
0
0
4376
acorn-jsx@5.3.2(acorn@8.15.0):
4377
dependencies:
4378
acorn: 8.15.0
···
4783
4784
esutils@2.0.3: {}
4785
0
0
4786
exsolve@1.0.8: {}
4787
4788
fast-deep-equal@3.1.3: {}
···
4822
4823
geojson-vt@4.0.2: {}
4824
0
0
0
0
4825
gl-matrix@3.4.4: {}
4826
4827
glob-parent@6.0.2:
···
4891
4892
isexe@2.0.0: {}
4893
0
0
4894
iso-datestring-validator@2.2.2: {}
4895
4896
jiti@2.6.1: {}
4897
0
0
4898
js-tokens@4.0.0: {}
4899
4900
js-yaml@4.1.1:
···
5165
color-name: 1.1.4
5166
hex-rgb: 4.3.0
5167
0
0
5168
parse5-htmlparser2-tree-adapter@7.1.0:
5169
dependencies:
5170
domhandler: 5.0.3
···
5196
5197
picomatch@4.0.3: {}
5198
0
0
0
0
0
0
0
0
0
0
0
0
0
5199
pkg-types@1.3.1:
5200
dependencies:
5201
confbox: 0.1.8
···
5710
5711
tiny-inflate@1.0.3: {}
5712
0
0
5713
tinyglobby@0.2.15:
5714
dependencies:
5715
fdir: 6.5.0(picomatch@4.0.3)
···
128
perfect-freehand:
129
specifier: ^1.2.2
130
version: 1.2.2
131
+
pixi.js:
132
+
specifier: ^8.16.0
133
+
version: 8.16.0
134
plyr:
135
specifier: ^3.8.4
136
version: 3.8.4
···
981
peerDependencies:
982
svelte: ^4 || ^5
983
984
+
'@pixi/colord@2.9.6':
985
+
resolution: {integrity: sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==, tarball: https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz}
986
+
987
'@polka/url@1.0.0-next.29':
988
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
989
···
1514
'@types/cookie@0.6.0':
1515
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
1516
1517
+
'@types/earcut@3.0.0':
1518
+
resolution: {integrity: sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ==, tarball: https://registry.npmjs.org/@types/earcut/-/earcut-3.0.0.tgz}
1519
+
1520
'@types/estree@1.0.8':
1521
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
1522
···
1633
'@webgpu/types@0.1.69':
1634
resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==}
1635
1636
+
'@xmldom/xmldom@0.8.11':
1637
+
resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==, tarball: https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz}
1638
+
engines: {node: '>=10.0.0'}
1639
+
1640
acorn-jsx@5.3.2:
1641
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
1642
peerDependencies:
···
1866
resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==}
1867
1868
earcut@3.0.2:
1869
+
resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==, tarball: https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz}
1870
1871
emoji-picker-element@1.28.1:
1872
resolution: {integrity: sha512-8c64IPish2PWoV9oYCo2pvuPHwIv+uK9bO0dfpPyMupDAvaWL9ZvYhWNTAR+2sx7BhfRjciImqP6CIUgNX+DMg==}
···
1977
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
1978
engines: {node: '>=0.10.0'}
1979
1980
+
eventemitter3@5.0.4:
1981
+
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, tarball: https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz}
1982
+
1983
exsolve@1.0.8:
1984
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
1985
···
2033
geojson-vt@4.0.2:
2034
resolution: {integrity: sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==}
2035
2036
+
gifuct-js@2.1.2:
2037
+
resolution: {integrity: sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==, tarball: https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz}
2038
+
2039
gl-matrix@3.4.4:
2040
resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==}
2041
···
2121
isexe@2.0.0:
2122
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
2123
2124
+
ismobilejs@1.1.1:
2125
+
resolution: {integrity: sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==, tarball: https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz}
2126
+
2127
iso-datestring-validator@2.2.2:
2128
resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==}
2129
···
2131
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
2132
hasBin: true
2133
2134
+
js-binary-schema-parser@2.0.3:
2135
+
resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==, tarball: https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz}
2136
+
2137
js-tokens@4.0.0:
2138
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, tarball: https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz}
2139
···
2410
parse-css-color@0.2.1:
2411
resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==}
2412
2413
+
parse-svg-path@0.1.2:
2414
+
resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==, tarball: https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz}
2415
+
2416
parse5-htmlparser2-tree-adapter@7.1.0:
2417
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
2418
···
2450
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
2451
engines: {node: '>=12'}
2452
2453
+
pixi.js@8.16.0:
2454
+
resolution: {integrity: sha512-gu2xw3sZGAn3cWBtk0HqTQT+v19YAfiaYXwUGgWoJl5NKz4cEZJUgWrwkmdfDszGyYBAGqOvJNbd2M9+vzLLMg==, tarball: https://registry.npmjs.org/pixi.js/-/pixi.js-8.16.0.tgz}
2455
+
2456
pkg-types@1.3.1:
2457
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
2458
···
2928
tiny-inflate@1.0.3:
2929
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
2930
2931
+
tiny-lru@11.4.7:
2932
+
resolution: {integrity: sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==, tarball: https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz}
2933
+
engines: {node: '>=12'}
2934
+
2935
tinyglobby@0.2.15:
2936
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
2937
engines: {node: '>=12.0.0'}
···
3785
number-flow: 0.5.9
3786
svelte: 5.48.0
3787
3788
+
'@pixi/colord@2.9.6': {}
3789
+
3790
'@polka/url@1.0.0-next.29': {}
3791
3792
'@poppinss/colors@4.1.6':
···
4261
4262
'@types/cookie@0.6.0': {}
4263
4264
+
'@types/earcut@3.0.0': {}
4265
+
4266
'@types/estree@1.0.8': {}
4267
4268
'@types/geojson-vt@3.2.5':
···
4412
4413
'@webgpu/types@0.1.69': {}
4414
4415
+
'@xmldom/xmldom@0.8.11': {}
4416
+
4417
acorn-jsx@5.3.2(acorn@8.15.0):
4418
dependencies:
4419
acorn: 8.15.0
···
4824
4825
esutils@2.0.3: {}
4826
4827
+
eventemitter3@5.0.4: {}
4828
+
4829
exsolve@1.0.8: {}
4830
4831
fast-deep-equal@3.1.3: {}
···
4865
4866
geojson-vt@4.0.2: {}
4867
4868
+
gifuct-js@2.1.2:
4869
+
dependencies:
4870
+
js-binary-schema-parser: 2.0.3
4871
+
4872
gl-matrix@3.4.4: {}
4873
4874
glob-parent@6.0.2:
···
4938
4939
isexe@2.0.0: {}
4940
4941
+
ismobilejs@1.1.1: {}
4942
+
4943
iso-datestring-validator@2.2.2: {}
4944
4945
jiti@2.6.1: {}
4946
4947
+
js-binary-schema-parser@2.0.3: {}
4948
+
4949
js-tokens@4.0.0: {}
4950
4951
js-yaml@4.1.1:
···
5216
color-name: 1.1.4
5217
hex-rgb: 4.3.0
5218
5219
+
parse-svg-path@0.1.2: {}
5220
+
5221
parse5-htmlparser2-tree-adapter@7.1.0:
5222
dependencies:
5223
domhandler: 5.0.3
···
5249
5250
picomatch@4.0.3: {}
5251
5252
+
pixi.js@8.16.0:
5253
+
dependencies:
5254
+
'@pixi/colord': 2.9.6
5255
+
'@types/earcut': 3.0.0
5256
+
'@webgpu/types': 0.1.69
5257
+
'@xmldom/xmldom': 0.8.11
5258
+
earcut: 3.0.2
5259
+
eventemitter3: 5.0.4
5260
+
gifuct-js: 2.1.2
5261
+
ismobilejs: 1.1.1
5262
+
parse-svg-path: 0.1.2
5263
+
tiny-lru: 11.4.7
5264
+
5265
pkg-types@1.3.1:
5266
dependencies:
5267
confbox: 0.1.8
···
5776
5777
tiny-inflate@1.0.3: {}
5778
5779
+
tiny-lru@11.4.7: {}
5780
+
5781
tinyglobby@0.2.15:
5782
dependencies:
5783
fdir: 6.5.0(picomatch@4.0.3)
+2
src/lib/cards/index.ts
···
29
import { EventCardDefinition } from './social/EventCard';
30
import { VCardCardDefinition } from './social/VCardCard';
31
import { DrawCardDefinition } from './visual/DrawCard';
0
32
import { TimerCardDefinition } from './utilities/TimerCard';
33
import { ClockCardDefinition } from './utilities/ClockCard';
34
import { CountdownCardDefinition } from './utilities/CountdownCard';
···
75
EventCardDefinition,
76
VCardCardDefinition,
77
DrawCardDefinition,
0
78
TimerCardDefinition,
79
ClockCardDefinition,
80
CountdownCardDefinition,
···
29
import { EventCardDefinition } from './social/EventCard';
30
import { VCardCardDefinition } from './social/VCardCard';
31
import { DrawCardDefinition } from './visual/DrawCard';
32
+
import { RecordVisualizerCardDefinition } from './visual/RecordVisualizerCard';
33
import { TimerCardDefinition } from './utilities/TimerCard';
34
import { ClockCardDefinition } from './utilities/ClockCard';
35
import { CountdownCardDefinition } from './utilities/CountdownCard';
···
76
EventCardDefinition,
77
VCardCardDefinition,
78
DrawCardDefinition,
79
+
RecordVisualizerCardDefinition,
80
TimerCardDefinition,
81
ClockCardDefinition,
82
CountdownCardDefinition,
+277
src/lib/cards/visual/RecordVisualizerCard/RecordVisualizerCard.svelte
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
<script lang="ts">
2
+
import { onMount } from 'svelte';
3
+
import { browser } from '$app/environment';
4
+
import * as PIXI from 'pixi.js';
5
+
import type { ContentComponentProps } from '../../types';
6
+
7
+
let { item }: ContentComponentProps = $props();
8
+
9
+
type RecordVisualizerCardData = {
10
+
emoji?: string;
11
+
collection?: string;
12
+
direction?: 'down' | 'up';
13
+
speed?: number;
14
+
};
15
+
16
+
let cardData = $derived(item.cardData as RecordVisualizerCardData);
17
+
18
+
let emoji = $derived(cardData.emoji || '๐');
19
+
let collection = $derived(cardData.collection || 'app.bsky.feed.like');
20
+
let direction = $derived(cardData.direction || 'down');
21
+
let speed = $derived(Math.max(0.5, Math.min(2, cardData.speed || 1)));
22
+
23
+
let containerEl: HTMLDivElement | null = null;
24
+
let canvasEl: HTMLCanvasElement | null = null;
25
+
let app: PIXI.Application | null = null;
26
+
let ws: WebSocket | null = null;
27
+
let prevCollection = $state<string | null>(null);
28
+
let prevEmoji = $state<string | null>(null);
29
+
let reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
30
+
31
+
const RECONNECT_DEBOUNCE = 1000;
32
+
const MAX_PARTICLES = 10000;
33
+
34
+
// Particle system
35
+
interface ParticleSprite extends PIXI.Sprite {
36
+
speedX: number;
37
+
speedY: number;
38
+
age: number;
39
+
maxAge: number;
40
+
initialSize: number;
41
+
}
42
+
43
+
let particles: ParticleSprite[] = [];
44
+
let particlePool: ParticleSprite[] = [];
45
+
let particleContainer: PIXI.Container | null = null;
46
+
let emojiTexture: PIXI.Texture | null = null;
47
+
48
+
function createEmojiTexture(emojiChar: string): PIXI.Texture {
49
+
const canvas = document.createElement('canvas');
50
+
const size = 64;
51
+
canvas.width = size;
52
+
canvas.height = size;
53
+
const ctx = canvas.getContext('2d')!;
54
+
ctx.font = `${size * 0.8}px serif`;
55
+
ctx.textAlign = 'center';
56
+
ctx.textBaseline = 'middle';
57
+
ctx.fillText(emojiChar, size / 2, size / 2);
58
+
return PIXI.Texture.from(canvas);
59
+
}
60
+
61
+
function spawnParticle() {
62
+
if (!app || !particleContainer || !emojiTexture) return;
63
+
64
+
let particle: ParticleSprite;
65
+
if (particlePool.length > 0) {
66
+
particle = particlePool.pop()!;
67
+
particle.texture = emojiTexture;
68
+
} else if (particles.length < MAX_PARTICLES) {
69
+
particle = new PIXI.Sprite(emojiTexture) as ParticleSprite;
70
+
particle.anchor.set(0.5, 0.5);
71
+
particleContainer.addChild(particle);
72
+
} else {
73
+
return;
74
+
}
75
+
76
+
const w = app.screen.width;
77
+
const h = app.screen.height;
78
+
79
+
// Parallax: random scale from 0.3 (far/small) to 1.0 (near/large)
80
+
const scale = Math.random() * 0.7 + 0.3;
81
+
const baseSize = (Math.random() * 30 + 15) * scale;
82
+
83
+
particle.visible = true;
84
+
particle.x = Math.random() * w;
85
+
particle.y = direction === 'down' ? -baseSize : h + baseSize;
86
+
particle.width = particle.height = baseSize;
87
+
particle.alpha = 0.4 + scale * 0.6;
88
+
particle.rotation = (Math.random() - 0.5) * 0.3;
89
+
particle.zIndex = Math.round(scale * 10);
90
+
91
+
// Speed based on scale (smaller = slower for parallax)
92
+
const baseSpeed = 80 * speed;
93
+
const effectiveSpeed = baseSpeed * scale;
94
+
particle.speedX = (Math.random() - 0.5) * 20;
95
+
particle.speedY = direction === 'down' ? effectiveSpeed : -effectiveSpeed;
96
+
97
+
particle.age = 0;
98
+
particle.maxAge = (h + baseSize * 2) / effectiveSpeed + 2;
99
+
particle.initialSize = baseSize;
100
+
101
+
particles.push(particle);
102
+
}
103
+
104
+
function removeParticle(particle: ParticleSprite) {
105
+
const index = particles.indexOf(particle);
106
+
if (index !== -1) {
107
+
particle.visible = false;
108
+
particles.splice(index, 1);
109
+
particlePool.push(particle);
110
+
}
111
+
}
112
+
113
+
function updateParticles(deltaTime: number) {
114
+
if (!app) return;
115
+
const h = app.screen.height;
116
+
117
+
for (let i = particles.length - 1; i >= 0; i--) {
118
+
const particle = particles[i];
119
+
particle.x += particle.speedX * deltaTime;
120
+
particle.y += particle.speedY * deltaTime;
121
+
particle.age += deltaTime;
122
+
123
+
// Remove if off screen or too old
124
+
const isOffScreen =
125
+
direction === 'down'
126
+
? particle.y > h + particle.initialSize
127
+
: particle.y < -particle.initialSize;
128
+
129
+
if (particle.age >= particle.maxAge || isOffScreen) {
130
+
removeParticle(particle);
131
+
}
132
+
}
133
+
}
134
+
135
+
async function initPixi() {
136
+
if (!browser || !containerEl || !canvasEl) return;
137
+
138
+
// Clean up existing app
139
+
if (app) {
140
+
app.destroy(true, { children: true, texture: true });
141
+
app = null;
142
+
}
143
+
144
+
particles = [];
145
+
particlePool = [];
146
+
147
+
app = new PIXI.Application();
148
+
await app.init({
149
+
canvas: canvasEl,
150
+
width: containerEl.clientWidth,
151
+
height: containerEl.clientHeight,
152
+
backgroundAlpha: 0,
153
+
antialias: true,
154
+
resolution: window.devicePixelRatio || 1,
155
+
autoDensity: true
156
+
});
157
+
158
+
particleContainer = new PIXI.Container();
159
+
particleContainer.sortableChildren = true;
160
+
app.stage.addChild(particleContainer);
161
+
162
+
emojiTexture = createEmojiTexture(emoji);
163
+
164
+
app.ticker.add((ticker) => {
165
+
updateParticles(ticker.deltaMS * 0.001);
166
+
});
167
+
168
+
// Handle resize
169
+
const resizeObserver = new ResizeObserver(() => {
170
+
if (app && containerEl) {
171
+
app.renderer.resize(containerEl.clientWidth, containerEl.clientHeight);
172
+
}
173
+
});
174
+
resizeObserver.observe(containerEl);
175
+
176
+
return () => {
177
+
resizeObserver.disconnect();
178
+
};
179
+
}
180
+
181
+
function connectWebSocket() {
182
+
if (!browser) return;
183
+
184
+
if (ws) {
185
+
ws.close();
186
+
}
187
+
188
+
const wsUrl = `wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=${encodeURIComponent(collection)}`;
189
+
190
+
try {
191
+
ws = new WebSocket(wsUrl);
192
+
193
+
ws.onmessage = (event) => {
194
+
try {
195
+
const data = JSON.parse(event.data);
196
+
if (data.kind === 'commit' && data.commit?.operation === 'create') {
197
+
spawnParticle();
198
+
}
199
+
} catch {
200
+
// Ignore parse errors
201
+
}
202
+
};
203
+
204
+
ws.onerror = () => {
205
+
// Silently handle errors
206
+
};
207
+
208
+
ws.onclose = () => {
209
+
setTimeout(() => {
210
+
if (containerEl) {
211
+
connectWebSocket();
212
+
}
213
+
}, 5000);
214
+
};
215
+
} catch {
216
+
// Failed to create WebSocket
217
+
}
218
+
}
219
+
220
+
onMount(() => {
221
+
let cleanupResize: (() => void) | undefined;
222
+
223
+
initPixi().then((cleanup) => {
224
+
cleanupResize = cleanup;
225
+
});
226
+
connectWebSocket();
227
+
228
+
return () => {
229
+
if (ws) {
230
+
ws.close();
231
+
ws = null;
232
+
}
233
+
if (reconnectTimeout) {
234
+
clearTimeout(reconnectTimeout);
235
+
}
236
+
if (app) {
237
+
app.destroy(true, { children: true, texture: true });
238
+
app = null;
239
+
}
240
+
cleanupResize?.();
241
+
};
242
+
});
243
+
244
+
// Reconnect when collection changes (debounced)
245
+
$effect(() => {
246
+
const currentCollection = collection;
247
+
248
+
if (prevCollection !== null && prevCollection !== currentCollection) {
249
+
if (reconnectTimeout) {
250
+
clearTimeout(reconnectTimeout);
251
+
}
252
+
reconnectTimeout = setTimeout(() => {
253
+
if (ws) {
254
+
ws.close();
255
+
}
256
+
connectWebSocket();
257
+
}, RECONNECT_DEBOUNCE);
258
+
}
259
+
260
+
prevCollection = currentCollection;
261
+
});
262
+
263
+
// Update emoji texture when emoji changes
264
+
$effect(() => {
265
+
const currentEmoji = emoji;
266
+
267
+
if (prevEmoji !== null && prevEmoji !== currentEmoji && app) {
268
+
emojiTexture = createEmojiTexture(currentEmoji);
269
+
}
270
+
271
+
prevEmoji = currentEmoji;
272
+
});
273
+
</script>
274
+
275
+
<div bind:this={containerEl} class="h-full w-full overflow-hidden">
276
+
<canvas bind:this={canvasEl} class="h-full w-full"></canvas>
277
+
</div>
+71
src/lib/cards/visual/RecordVisualizerCard/RecordVisualizerSettings.svelte
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
<script lang="ts">
2
+
import type { Item } from '$lib/types';
3
+
import type { SettingsComponentProps } from '../../types';
4
+
import { Input, Label } from '@foxui/core';
5
+
6
+
let { item = $bindable<Item>() }: SettingsComponentProps = $props();
7
+
8
+
type RecordVisualizerCardData = {
9
+
emoji?: string;
10
+
collection?: string;
11
+
direction?: 'down' | 'up';
12
+
speed?: number;
13
+
};
14
+
15
+
let cardData = $derived(item.cardData as RecordVisualizerCardData);
16
+
17
+
// Initialize defaults if not set
18
+
if (item.cardData.emoji === undefined) {
19
+
item.cardData.emoji = '๐';
20
+
}
21
+
if (item.cardData.collection === undefined) {
22
+
item.cardData.collection = 'app.bsky.feed.like';
23
+
}
24
+
if (item.cardData.direction === undefined) {
25
+
item.cardData.direction = 'down';
26
+
}
27
+
if (item.cardData.speed === undefined) {
28
+
item.cardData.speed = 1;
29
+
}
30
+
</script>
31
+
32
+
<div class="flex flex-col gap-3">
33
+
<div>
34
+
<Label class="mb-1 text-xs">Emoji</Label>
35
+
<Input bind:value={item.cardData.emoji} placeholder="๐" class="w-full" />
36
+
</div>
37
+
38
+
<div>
39
+
<Label class="mb-1 text-xs">Collection</Label>
40
+
<Input bind:value={item.cardData.collection} placeholder="app.bsky.feed.like" class="w-full" />
41
+
</div>
42
+
43
+
<div>
44
+
<Label class="mb-1 text-xs">Direction</Label>
45
+
<select
46
+
value={cardData.direction ?? 'down'}
47
+
onchange={(e) => {
48
+
item.cardData.direction = (e.target as HTMLSelectElement).value;
49
+
}}
50
+
class="bg-base-100 dark:bg-base-800 border-base-300 dark:border-base-600 w-full rounded-md border px-3 py-2 text-sm"
51
+
>
52
+
<option value="down">Down</option>
53
+
<option value="up">Up</option>
54
+
</select>
55
+
</div>
56
+
57
+
<div>
58
+
<Label class="mb-1 text-xs">Speed ({cardData.speed?.toFixed(1) ?? '1.0'}x)</Label>
59
+
<input
60
+
type="range"
61
+
min="0.5"
62
+
max="2"
63
+
step="0.1"
64
+
value={cardData.speed ?? 1}
65
+
oninput={(e) => {
66
+
item.cardData.speed = parseFloat(e.currentTarget.value);
67
+
}}
68
+
class="bg-base-200 dark:bg-base-700 h-2 w-full cursor-pointer appearance-none rounded-lg"
69
+
/>
70
+
</div>
71
+
</div>
+30
src/lib/cards/visual/RecordVisualizerCard/index.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
import type { CardDefinition } from '../../types';
2
+
import RecordVisualizerCard from './RecordVisualizerCard.svelte';
3
+
import RecordVisualizerSettings from './RecordVisualizerSettings.svelte';
4
+
5
+
export const RecordVisualizerCardDefinition = {
6
+
type: 'record-visualizer',
7
+
contentComponent: RecordVisualizerCard,
8
+
createNew: (card) => {
9
+
card.cardType = 'record-visualizer';
10
+
card.cardData = {
11
+
emoji: '๐',
12
+
collection: 'app.bsky.feed.like',
13
+
direction: 'down',
14
+
speed: 1
15
+
};
16
+
card.w = 2;
17
+
card.h = 2;
18
+
card.mobileW = 4;
19
+
card.mobileH = 4;
20
+
},
21
+
settingsComponent: RecordVisualizerSettings,
22
+
minW: 1,
23
+
minH: 2,
24
+
canHaveLabel: true,
25
+
26
+
keywords: ['emoji', 'particles', 'animation', 'bluesky', 'atproto', 'live', 'realtime', 'stream'],
27
+
groups: ['Visual'],
28
+
name: 'Record Visualizer',
29
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z" /></svg>`
30
+
} as CardDefinition & { type: 'record-visualizer' };