a web component that shows the replies to a linked bsky post as comments.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

first commit

rekkice 30d41c77

+2267
+5
.envrc
··· 1 + #!/usr/bin/env bash 2 + 3 + eval "$(devenv direnvrc)" 4 + 5 + use devenv
+23
.github/workflows/test.yml
··· 1 + name: test 2 + 3 + on: 4 + push: 5 + branches: 6 + - master 7 + - main 8 + pull_request: 9 + 10 + jobs: 11 + test: 12 + runs-on: ubuntu-latest 13 + steps: 14 + - uses: actions/checkout@v6 15 + - uses: erlef/setup-beam@v1 16 + with: 17 + otp-version: "28" 18 + gleam-version: "1.15.2" 19 + rebar3-version: "3" 20 + # elixir-version: "1" 21 + - run: gleam deps download 22 + - run: gleam test 23 + - run: gleam format --check src test
+35
.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump 5 + .devenv* 6 + .direnv* 7 + 8 + #Added automatically by Lustre Dev Tools 9 + /.lustre 10 + /dist 11 + 12 + # Logs 13 + logs 14 + *.log 15 + npm-debug.log* 16 + yarn-debug.log* 17 + yarn-error.log* 18 + pnpm-debug.log* 19 + lerna-debug.log* 20 + 21 + node_modules 22 + dist 23 + dist-ssr 24 + *.local 25 + 26 + # Editor directories and files 27 + .vscode/* 28 + !.vscode/extensions.json 29 + .idea 30 + .DS_Store 31 + *.suo 32 + *.ntvs* 33 + *.njsproj 34 + *.sln 35 + *.sw?
+24
README.md
··· 1 + # bsky_comments_widget 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/bsky_comments_widget)](https://hex.pm/packages/bsky_comments_widget) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/bsky_comments_widget/) 5 + 6 + ```sh 7 + gleam add bsky_comments_widget@1 8 + ``` 9 + ```gleam 10 + import bsky_comments_widget 11 + 12 + pub fn main() -> Nil { 13 + // TODO: An example of the project in use 14 + } 15 + ``` 16 + 17 + Further documentation can be found at <https://hexdocs.pm/bsky_comments_widget>. 18 + 19 + ## Development 20 + 21 + ```sh 22 + gleam run # Run the project 23 + gleam test # Run the tests 24 + ```
+82
devenv.lock
··· 1 + { 2 + "nodes": { 3 + "devenv": { 4 + "locked": { 5 + "dir": "src/modules", 6 + "lastModified": 1774876861, 7 + "narHash": "sha256-xOMhEthD6VrCphSPFqCMOQzpld64nfhNJj1QPs95UKE=", 8 + "owner": "cachix", 9 + "repo": "devenv", 10 + "rev": "7d7c59a563c75c85fdf21d533e990ad81ae7798c", 11 + "type": "github" 12 + }, 13 + "original": { 14 + "dir": "src/modules", 15 + "owner": "cachix", 16 + "repo": "devenv", 17 + "type": "github" 18 + } 19 + }, 20 + "nixpkgs": { 21 + "inputs": { 22 + "nixpkgs-src": "nixpkgs-src" 23 + }, 24 + "locked": { 25 + "lastModified": 1774287239, 26 + "narHash": "sha256-W3krsWcDwYuA3gPWsFA24YAXxOFUL6iIlT6IknAoNSE=", 27 + "owner": "cachix", 28 + "repo": "devenv-nixpkgs", 29 + "rev": "fa7125ea7f1ae5430010a6e071f68375a39bd24c", 30 + "type": "github" 31 + }, 32 + "original": { 33 + "owner": "cachix", 34 + "ref": "rolling", 35 + "repo": "devenv-nixpkgs", 36 + "type": "github" 37 + } 38 + }, 39 + "nixpkgs-src": { 40 + "flake": false, 41 + "locked": { 42 + "lastModified": 1773840656, 43 + "narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=", 44 + "owner": "NixOS", 45 + "repo": "nixpkgs", 46 + "rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512", 47 + "type": "github" 48 + }, 49 + "original": { 50 + "owner": "NixOS", 51 + "ref": "nixpkgs-unstable", 52 + "repo": "nixpkgs", 53 + "type": "github" 54 + } 55 + }, 56 + "nixpkgs-unstable": { 57 + "locked": { 58 + "lastModified": 1774709303, 59 + "narHash": "sha256-D3Q07BbIA2KnTcSXIqqu9P586uWxN74zNoCH3h2ESHg=", 60 + "owner": "NixOS", 61 + "repo": "nixpkgs", 62 + "rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685", 63 + "type": "github" 64 + }, 65 + "original": { 66 + "owner": "NixOS", 67 + "ref": "nixos-unstable", 68 + "repo": "nixpkgs", 69 + "type": "github" 70 + } 71 + }, 72 + "root": { 73 + "inputs": { 74 + "devenv": "devenv", 75 + "nixpkgs": "nixpkgs", 76 + "nixpkgs-unstable": "nixpkgs-unstable" 77 + } 78 + } 79 + }, 80 + "root": "root", 81 + "version": 7 82 + }
+20
devenv.nix
··· 1 + { pkgs, inputs, ... }: 2 + 3 + let 4 + pkgs-unstable = import inputs.nixpkgs-unstable { system = pkgs.stdenv.system; }; 5 + in 6 + { 7 + packages = with pkgs-unstable; [ 8 + gleam 9 + erlang 10 + nodejs 11 + pnpm 12 + 13 + inotify-tools 14 + ]; 15 + 16 + languages.javascript.enable = true; 17 + languages.javascript.pnpm.enable = true; 18 + } 19 + 20 +
+4
devenv.yaml
··· 1 + inputs: 2 + nixpkgs-unstable: 3 + url: github:NixOS/nixpkgs/nixos-unstable 4 +
+27
gleam.toml
··· 1 + name = "bsky_comments_widget" 2 + version = "1.0.0" 3 + target = "javascript" 4 + 5 + # Fill out these fields if you intend to generate HTML documentation or publish 6 + # your project to the Hex package manager. 7 + # 8 + description = "a web component that shows the replies to a linked bsky post as comments." 9 + licences = ["MIT"] 10 + repository = { type = "tangled", user = "lekkice.moe", repo = "bsky_comments_widget" } 11 + # links = [{ title = "Website", href = "" }] 12 + # 13 + # For a full reference of all the available options, you can have a look at 14 + # https://gleam.run/writing-gleam/gleam-toml/. 15 + 16 + [dependencies] 17 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 18 + lustre = ">= 5.6.0 and < 6.0.0" 19 + rsvp = ">= 1.2.0 and < 2.0.0" 20 + gleam_fetch = ">= 1.3.1 and < 2.0.0" 21 + gleam_http = ">= 4.3.0 and < 5.0.0" 22 + gleam_javascript = ">= 1.0.0 and < 2.0.0" 23 + gleam_json = ">= 3.1.0 and < 4.0.0" 24 + gleam_regexp = ">= 1.1.1 and < 2.0.0" 25 + 26 + [dev_dependencies] 27 + gleeunit = ">= 1.0.0 and < 2.0.0"
+29
index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>bsky_comments_widget</title> 8 + </head> 9 + 10 + <style> 11 + .comments-container { 12 + padding: 8rem; 13 + padding-top: 2rem; 14 + width: 100%; 15 + max-width: 56rem; 16 + margin-left: auto; 17 + margin-right: auto; 18 + } 19 + </style> 20 + 21 + <body> 22 + <div id="app"></div> 23 + <script type="module" src="/src/main.ts"></script> 24 + <div class="comments-container"> 25 + <bsky-comments post_url="https://bsky.app/profile/mrgan.com/post/3michx3lqa22y" /> 26 + </div> 27 + </body> 28 + 29 + </html>
+29
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 6 + { name = "gleam_fetch", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "A8FEB5FC4F9C4C72A71BA0D7AC249CF3AE4E98A4123607A5077D8C0B8ECC5A40" }, 7 + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, 8 + { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, 9 + { name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" }, 10 + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 11 + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 12 + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, 13 + { name = "gleam_stdlib", version = "0.70.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86949BF5D1F0E4AC0AB5B06F235D8A5CC11A2DFC33BF22F752156ED61CA7D0FF" }, 14 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 15 + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, 16 + { name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" }, 17 + { name = "rsvp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_fetch", "gleam_http", "gleam_httpc", "gleam_javascript", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "rsvp", source = "hex", outer_checksum = "40F9E0E662FF258E10C7041A9591261FE802D56625FB444B91510969644F7722" }, 18 + ] 19 + 20 + [requirements] 21 + gleam_fetch = { version = ">= 1.3.1 and < 2.0.0" } 22 + gleam_http = { version = ">= 4.3.0 and < 5.0.0" } 23 + gleam_javascript = { version = ">= 1.0.0 and < 2.0.0" } 24 + gleam_json = { version = ">= 3.1.0 and < 4.0.0" } 25 + gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } 26 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 27 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 28 + lustre = { version = ">= 5.6.0 and < 6.0.0" } 29 + rsvp = { version = ">= 1.2.0 and < 2.0.0" }
+20
package.json
··· 1 + { 2 + "name": "bsky_comments_widget", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite", 8 + "build": "vite build", 9 + "preview": "vite preview" 10 + }, 11 + "devDependencies": { 12 + "typescript": "~5.9.3", 13 + "vite": "^8.0.1" 14 + }, 15 + "dependencies": { 16 + "@tailwindcss/vite": "^4.2.2", 17 + "tailwindcss": "^4.2.2", 18 + "vite-gleam": "^1.7.1" 19 + } 20 + }
+1360
pnpm-lock.yaml
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + dependencies: 11 + '@tailwindcss/vite': 12 + specifier: ^4.2.2 13 + version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(esbuild@0.27.4)(jiti@2.6.1)) 14 + tailwindcss: 15 + specifier: ^4.2.2 16 + version: 4.2.2 17 + vite-gleam: 18 + specifier: ^1.7.1 19 + version: 1.7.1(jiti@2.6.1)(lightningcss@1.32.0) 20 + devDependencies: 21 + typescript: 22 + specifier: ~5.9.3 23 + version: 5.9.3 24 + vite: 25 + specifier: ^8.0.1 26 + version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(esbuild@0.27.4)(jiti@2.6.1) 27 + 28 + packages: 29 + 30 + '@emnapi/core@1.9.1': 31 + resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} 32 + 33 + '@emnapi/runtime@1.9.1': 34 + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} 35 + 36 + '@emnapi/wasi-threads@1.2.0': 37 + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} 38 + 39 + '@esbuild/aix-ppc64@0.27.4': 40 + resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} 41 + engines: {node: '>=18'} 42 + cpu: [ppc64] 43 + os: [aix] 44 + 45 + '@esbuild/android-arm64@0.27.4': 46 + resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} 47 + engines: {node: '>=18'} 48 + cpu: [arm64] 49 + os: [android] 50 + 51 + '@esbuild/android-arm@0.27.4': 52 + resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} 53 + engines: {node: '>=18'} 54 + cpu: [arm] 55 + os: [android] 56 + 57 + '@esbuild/android-x64@0.27.4': 58 + resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} 59 + engines: {node: '>=18'} 60 + cpu: [x64] 61 + os: [android] 62 + 63 + '@esbuild/darwin-arm64@0.27.4': 64 + resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} 65 + engines: {node: '>=18'} 66 + cpu: [arm64] 67 + os: [darwin] 68 + 69 + '@esbuild/darwin-x64@0.27.4': 70 + resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} 71 + engines: {node: '>=18'} 72 + cpu: [x64] 73 + os: [darwin] 74 + 75 + '@esbuild/freebsd-arm64@0.27.4': 76 + resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} 77 + engines: {node: '>=18'} 78 + cpu: [arm64] 79 + os: [freebsd] 80 + 81 + '@esbuild/freebsd-x64@0.27.4': 82 + resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} 83 + engines: {node: '>=18'} 84 + cpu: [x64] 85 + os: [freebsd] 86 + 87 + '@esbuild/linux-arm64@0.27.4': 88 + resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} 89 + engines: {node: '>=18'} 90 + cpu: [arm64] 91 + os: [linux] 92 + 93 + '@esbuild/linux-arm@0.27.4': 94 + resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} 95 + engines: {node: '>=18'} 96 + cpu: [arm] 97 + os: [linux] 98 + 99 + '@esbuild/linux-ia32@0.27.4': 100 + resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} 101 + engines: {node: '>=18'} 102 + cpu: [ia32] 103 + os: [linux] 104 + 105 + '@esbuild/linux-loong64@0.27.4': 106 + resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} 107 + engines: {node: '>=18'} 108 + cpu: [loong64] 109 + os: [linux] 110 + 111 + '@esbuild/linux-mips64el@0.27.4': 112 + resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} 113 + engines: {node: '>=18'} 114 + cpu: [mips64el] 115 + os: [linux] 116 + 117 + '@esbuild/linux-ppc64@0.27.4': 118 + resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} 119 + engines: {node: '>=18'} 120 + cpu: [ppc64] 121 + os: [linux] 122 + 123 + '@esbuild/linux-riscv64@0.27.4': 124 + resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} 125 + engines: {node: '>=18'} 126 + cpu: [riscv64] 127 + os: [linux] 128 + 129 + '@esbuild/linux-s390x@0.27.4': 130 + resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} 131 + engines: {node: '>=18'} 132 + cpu: [s390x] 133 + os: [linux] 134 + 135 + '@esbuild/linux-x64@0.27.4': 136 + resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} 137 + engines: {node: '>=18'} 138 + cpu: [x64] 139 + os: [linux] 140 + 141 + '@esbuild/netbsd-arm64@0.27.4': 142 + resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} 143 + engines: {node: '>=18'} 144 + cpu: [arm64] 145 + os: [netbsd] 146 + 147 + '@esbuild/netbsd-x64@0.27.4': 148 + resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} 149 + engines: {node: '>=18'} 150 + cpu: [x64] 151 + os: [netbsd] 152 + 153 + '@esbuild/openbsd-arm64@0.27.4': 154 + resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} 155 + engines: {node: '>=18'} 156 + cpu: [arm64] 157 + os: [openbsd] 158 + 159 + '@esbuild/openbsd-x64@0.27.4': 160 + resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} 161 + engines: {node: '>=18'} 162 + cpu: [x64] 163 + os: [openbsd] 164 + 165 + '@esbuild/openharmony-arm64@0.27.4': 166 + resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} 167 + engines: {node: '>=18'} 168 + cpu: [arm64] 169 + os: [openharmony] 170 + 171 + '@esbuild/sunos-x64@0.27.4': 172 + resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} 173 + engines: {node: '>=18'} 174 + cpu: [x64] 175 + os: [sunos] 176 + 177 + '@esbuild/win32-arm64@0.27.4': 178 + resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} 179 + engines: {node: '>=18'} 180 + cpu: [arm64] 181 + os: [win32] 182 + 183 + '@esbuild/win32-ia32@0.27.4': 184 + resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} 185 + engines: {node: '>=18'} 186 + cpu: [ia32] 187 + os: [win32] 188 + 189 + '@esbuild/win32-x64@0.27.4': 190 + resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} 191 + engines: {node: '>=18'} 192 + cpu: [x64] 193 + os: [win32] 194 + 195 + '@jridgewell/gen-mapping@0.3.13': 196 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 197 + 198 + '@jridgewell/remapping@2.3.5': 199 + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} 200 + 201 + '@jridgewell/resolve-uri@3.1.2': 202 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 203 + engines: {node: '>=6.0.0'} 204 + 205 + '@jridgewell/sourcemap-codec@1.5.5': 206 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 207 + 208 + '@jridgewell/trace-mapping@0.3.31': 209 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 210 + 211 + '@napi-rs/wasm-runtime@1.1.2': 212 + resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==} 213 + peerDependencies: 214 + '@emnapi/core': ^1.7.1 215 + '@emnapi/runtime': ^1.7.1 216 + 217 + '@oxc-project/types@0.122.0': 218 + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} 219 + 220 + '@rolldown/binding-android-arm64@1.0.0-rc.12': 221 + resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} 222 + engines: {node: ^20.19.0 || >=22.12.0} 223 + cpu: [arm64] 224 + os: [android] 225 + 226 + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': 227 + resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} 228 + engines: {node: ^20.19.0 || >=22.12.0} 229 + cpu: [arm64] 230 + os: [darwin] 231 + 232 + '@rolldown/binding-darwin-x64@1.0.0-rc.12': 233 + resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} 234 + engines: {node: ^20.19.0 || >=22.12.0} 235 + cpu: [x64] 236 + os: [darwin] 237 + 238 + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': 239 + resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} 240 + engines: {node: ^20.19.0 || >=22.12.0} 241 + cpu: [x64] 242 + os: [freebsd] 243 + 244 + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': 245 + resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} 246 + engines: {node: ^20.19.0 || >=22.12.0} 247 + cpu: [arm] 248 + os: [linux] 249 + 250 + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': 251 + resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} 252 + engines: {node: ^20.19.0 || >=22.12.0} 253 + cpu: [arm64] 254 + os: [linux] 255 + libc: [glibc] 256 + 257 + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': 258 + resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} 259 + engines: {node: ^20.19.0 || >=22.12.0} 260 + cpu: [arm64] 261 + os: [linux] 262 + libc: [musl] 263 + 264 + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': 265 + resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} 266 + engines: {node: ^20.19.0 || >=22.12.0} 267 + cpu: [ppc64] 268 + os: [linux] 269 + libc: [glibc] 270 + 271 + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': 272 + resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} 273 + engines: {node: ^20.19.0 || >=22.12.0} 274 + cpu: [s390x] 275 + os: [linux] 276 + libc: [glibc] 277 + 278 + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': 279 + resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} 280 + engines: {node: ^20.19.0 || >=22.12.0} 281 + cpu: [x64] 282 + os: [linux] 283 + libc: [glibc] 284 + 285 + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': 286 + resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} 287 + engines: {node: ^20.19.0 || >=22.12.0} 288 + cpu: [x64] 289 + os: [linux] 290 + libc: [musl] 291 + 292 + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': 293 + resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} 294 + engines: {node: ^20.19.0 || >=22.12.0} 295 + cpu: [arm64] 296 + os: [openharmony] 297 + 298 + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': 299 + resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} 300 + engines: {node: '>=14.0.0'} 301 + cpu: [wasm32] 302 + 303 + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': 304 + resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} 305 + engines: {node: ^20.19.0 || >=22.12.0} 306 + cpu: [arm64] 307 + os: [win32] 308 + 309 + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': 310 + resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} 311 + engines: {node: ^20.19.0 || >=22.12.0} 312 + cpu: [x64] 313 + os: [win32] 314 + 315 + '@rolldown/pluginutils@1.0.0-rc.12': 316 + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} 317 + 318 + '@rollup/rollup-android-arm-eabi@4.60.1': 319 + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} 320 + cpu: [arm] 321 + os: [android] 322 + 323 + '@rollup/rollup-android-arm64@4.60.1': 324 + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} 325 + cpu: [arm64] 326 + os: [android] 327 + 328 + '@rollup/rollup-darwin-arm64@4.60.1': 329 + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} 330 + cpu: [arm64] 331 + os: [darwin] 332 + 333 + '@rollup/rollup-darwin-x64@4.60.1': 334 + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} 335 + cpu: [x64] 336 + os: [darwin] 337 + 338 + '@rollup/rollup-freebsd-arm64@4.60.1': 339 + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} 340 + cpu: [arm64] 341 + os: [freebsd] 342 + 343 + '@rollup/rollup-freebsd-x64@4.60.1': 344 + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} 345 + cpu: [x64] 346 + os: [freebsd] 347 + 348 + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': 349 + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} 350 + cpu: [arm] 351 + os: [linux] 352 + libc: [glibc] 353 + 354 + '@rollup/rollup-linux-arm-musleabihf@4.60.1': 355 + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} 356 + cpu: [arm] 357 + os: [linux] 358 + libc: [musl] 359 + 360 + '@rollup/rollup-linux-arm64-gnu@4.60.1': 361 + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} 362 + cpu: [arm64] 363 + os: [linux] 364 + libc: [glibc] 365 + 366 + '@rollup/rollup-linux-arm64-musl@4.60.1': 367 + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} 368 + cpu: [arm64] 369 + os: [linux] 370 + libc: [musl] 371 + 372 + '@rollup/rollup-linux-loong64-gnu@4.60.1': 373 + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} 374 + cpu: [loong64] 375 + os: [linux] 376 + libc: [glibc] 377 + 378 + '@rollup/rollup-linux-loong64-musl@4.60.1': 379 + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} 380 + cpu: [loong64] 381 + os: [linux] 382 + libc: [musl] 383 + 384 + '@rollup/rollup-linux-ppc64-gnu@4.60.1': 385 + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} 386 + cpu: [ppc64] 387 + os: [linux] 388 + libc: [glibc] 389 + 390 + '@rollup/rollup-linux-ppc64-musl@4.60.1': 391 + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} 392 + cpu: [ppc64] 393 + os: [linux] 394 + libc: [musl] 395 + 396 + '@rollup/rollup-linux-riscv64-gnu@4.60.1': 397 + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} 398 + cpu: [riscv64] 399 + os: [linux] 400 + libc: [glibc] 401 + 402 + '@rollup/rollup-linux-riscv64-musl@4.60.1': 403 + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} 404 + cpu: [riscv64] 405 + os: [linux] 406 + libc: [musl] 407 + 408 + '@rollup/rollup-linux-s390x-gnu@4.60.1': 409 + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} 410 + cpu: [s390x] 411 + os: [linux] 412 + libc: [glibc] 413 + 414 + '@rollup/rollup-linux-x64-gnu@4.60.1': 415 + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} 416 + cpu: [x64] 417 + os: [linux] 418 + libc: [glibc] 419 + 420 + '@rollup/rollup-linux-x64-musl@4.60.1': 421 + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} 422 + cpu: [x64] 423 + os: [linux] 424 + libc: [musl] 425 + 426 + '@rollup/rollup-openbsd-x64@4.60.1': 427 + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} 428 + cpu: [x64] 429 + os: [openbsd] 430 + 431 + '@rollup/rollup-openharmony-arm64@4.60.1': 432 + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} 433 + cpu: [arm64] 434 + os: [openharmony] 435 + 436 + '@rollup/rollup-win32-arm64-msvc@4.60.1': 437 + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} 438 + cpu: [arm64] 439 + os: [win32] 440 + 441 + '@rollup/rollup-win32-ia32-msvc@4.60.1': 442 + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} 443 + cpu: [ia32] 444 + os: [win32] 445 + 446 + '@rollup/rollup-win32-x64-gnu@4.60.1': 447 + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} 448 + cpu: [x64] 449 + os: [win32] 450 + 451 + '@rollup/rollup-win32-x64-msvc@4.60.1': 452 + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} 453 + cpu: [x64] 454 + os: [win32] 455 + 456 + '@tailwindcss/node@4.2.2': 457 + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} 458 + 459 + '@tailwindcss/oxide-android-arm64@4.2.2': 460 + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} 461 + engines: {node: '>= 20'} 462 + cpu: [arm64] 463 + os: [android] 464 + 465 + '@tailwindcss/oxide-darwin-arm64@4.2.2': 466 + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} 467 + engines: {node: '>= 20'} 468 + cpu: [arm64] 469 + os: [darwin] 470 + 471 + '@tailwindcss/oxide-darwin-x64@4.2.2': 472 + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} 473 + engines: {node: '>= 20'} 474 + cpu: [x64] 475 + os: [darwin] 476 + 477 + '@tailwindcss/oxide-freebsd-x64@4.2.2': 478 + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} 479 + engines: {node: '>= 20'} 480 + cpu: [x64] 481 + os: [freebsd] 482 + 483 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': 484 + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} 485 + engines: {node: '>= 20'} 486 + cpu: [arm] 487 + os: [linux] 488 + 489 + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': 490 + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} 491 + engines: {node: '>= 20'} 492 + cpu: [arm64] 493 + os: [linux] 494 + libc: [glibc] 495 + 496 + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': 497 + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} 498 + engines: {node: '>= 20'} 499 + cpu: [arm64] 500 + os: [linux] 501 + libc: [musl] 502 + 503 + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': 504 + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} 505 + engines: {node: '>= 20'} 506 + cpu: [x64] 507 + os: [linux] 508 + libc: [glibc] 509 + 510 + '@tailwindcss/oxide-linux-x64-musl@4.2.2': 511 + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} 512 + engines: {node: '>= 20'} 513 + cpu: [x64] 514 + os: [linux] 515 + libc: [musl] 516 + 517 + '@tailwindcss/oxide-wasm32-wasi@4.2.2': 518 + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} 519 + engines: {node: '>=14.0.0'} 520 + cpu: [wasm32] 521 + bundledDependencies: 522 + - '@napi-rs/wasm-runtime' 523 + - '@emnapi/core' 524 + - '@emnapi/runtime' 525 + - '@tybys/wasm-util' 526 + - '@emnapi/wasi-threads' 527 + - tslib 528 + 529 + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': 530 + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} 531 + engines: {node: '>= 20'} 532 + cpu: [arm64] 533 + os: [win32] 534 + 535 + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': 536 + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} 537 + engines: {node: '>= 20'} 538 + cpu: [x64] 539 + os: [win32] 540 + 541 + '@tailwindcss/oxide@4.2.2': 542 + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} 543 + engines: {node: '>= 20'} 544 + 545 + '@tailwindcss/vite@4.2.2': 546 + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} 547 + peerDependencies: 548 + vite: ^5.2.0 || ^6 || ^7 || ^8 549 + 550 + '@tybys/wasm-util@0.10.1': 551 + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} 552 + 553 + '@types/estree@1.0.8': 554 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 555 + 556 + detect-libc@2.1.2: 557 + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} 558 + engines: {node: '>=8'} 559 + 560 + enhanced-resolve@5.20.1: 561 + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} 562 + engines: {node: '>=10.13.0'} 563 + 564 + esbuild@0.27.4: 565 + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} 566 + engines: {node: '>=18'} 567 + hasBin: true 568 + 569 + fdir@6.5.0: 570 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 571 + engines: {node: '>=12.0.0'} 572 + peerDependencies: 573 + picomatch: ^3 || ^4 574 + peerDependenciesMeta: 575 + picomatch: 576 + optional: true 577 + 578 + fsevents@2.3.3: 579 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 580 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 581 + os: [darwin] 582 + 583 + graceful-fs@4.2.11: 584 + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 585 + 586 + jiti@2.6.1: 587 + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} 588 + hasBin: true 589 + 590 + lightningcss-android-arm64@1.32.0: 591 + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} 592 + engines: {node: '>= 12.0.0'} 593 + cpu: [arm64] 594 + os: [android] 595 + 596 + lightningcss-darwin-arm64@1.32.0: 597 + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} 598 + engines: {node: '>= 12.0.0'} 599 + cpu: [arm64] 600 + os: [darwin] 601 + 602 + lightningcss-darwin-x64@1.32.0: 603 + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} 604 + engines: {node: '>= 12.0.0'} 605 + cpu: [x64] 606 + os: [darwin] 607 + 608 + lightningcss-freebsd-x64@1.32.0: 609 + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} 610 + engines: {node: '>= 12.0.0'} 611 + cpu: [x64] 612 + os: [freebsd] 613 + 614 + lightningcss-linux-arm-gnueabihf@1.32.0: 615 + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} 616 + engines: {node: '>= 12.0.0'} 617 + cpu: [arm] 618 + os: [linux] 619 + 620 + lightningcss-linux-arm64-gnu@1.32.0: 621 + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} 622 + engines: {node: '>= 12.0.0'} 623 + cpu: [arm64] 624 + os: [linux] 625 + libc: [glibc] 626 + 627 + lightningcss-linux-arm64-musl@1.32.0: 628 + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} 629 + engines: {node: '>= 12.0.0'} 630 + cpu: [arm64] 631 + os: [linux] 632 + libc: [musl] 633 + 634 + lightningcss-linux-x64-gnu@1.32.0: 635 + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} 636 + engines: {node: '>= 12.0.0'} 637 + cpu: [x64] 638 + os: [linux] 639 + libc: [glibc] 640 + 641 + lightningcss-linux-x64-musl@1.32.0: 642 + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} 643 + engines: {node: '>= 12.0.0'} 644 + cpu: [x64] 645 + os: [linux] 646 + libc: [musl] 647 + 648 + lightningcss-win32-arm64-msvc@1.32.0: 649 + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} 650 + engines: {node: '>= 12.0.0'} 651 + cpu: [arm64] 652 + os: [win32] 653 + 654 + lightningcss-win32-x64-msvc@1.32.0: 655 + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} 656 + engines: {node: '>= 12.0.0'} 657 + cpu: [x64] 658 + os: [win32] 659 + 660 + lightningcss@1.32.0: 661 + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} 662 + engines: {node: '>= 12.0.0'} 663 + 664 + magic-string@0.30.21: 665 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 666 + 667 + nanoid@3.3.11: 668 + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 669 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 670 + hasBin: true 671 + 672 + picocolors@1.1.1: 673 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 674 + 675 + picomatch@4.0.4: 676 + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} 677 + engines: {node: '>=12'} 678 + 679 + postcss@8.5.8: 680 + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} 681 + engines: {node: ^10 || ^12 || >=14} 682 + 683 + rolldown@1.0.0-rc.12: 684 + resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} 685 + engines: {node: ^20.19.0 || >=22.12.0} 686 + hasBin: true 687 + 688 + rollup@4.60.1: 689 + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} 690 + engines: {node: '>=18.0.0', npm: '>=8.0.0'} 691 + hasBin: true 692 + 693 + source-map-js@1.2.1: 694 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 695 + engines: {node: '>=0.10.0'} 696 + 697 + tailwindcss@4.2.2: 698 + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} 699 + 700 + tapable@2.3.2: 701 + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} 702 + engines: {node: '>=6'} 703 + 704 + tinyglobby@0.2.15: 705 + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 706 + engines: {node: '>=12.0.0'} 707 + 708 + toml@3.0.0: 709 + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} 710 + 711 + tslib@2.8.1: 712 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 713 + 714 + typescript@5.9.3: 715 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} 716 + engines: {node: '>=14.17'} 717 + hasBin: true 718 + 719 + vite-gleam@1.7.1: 720 + resolution: {integrity: sha512-MiQ8uIPgMWH3vRxVLHG/FAcTFW37MpWhFRp8LHqKohKMJmXQJqtINA/GZGXb2CpgSx5MU13Osc4sHVmOxBWr3Q==} 721 + 722 + vite@7.3.1: 723 + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} 724 + engines: {node: ^20.19.0 || >=22.12.0} 725 + hasBin: true 726 + peerDependencies: 727 + '@types/node': ^20.19.0 || >=22.12.0 728 + jiti: '>=1.21.0' 729 + less: ^4.0.0 730 + lightningcss: ^1.21.0 731 + sass: ^1.70.0 732 + sass-embedded: ^1.70.0 733 + stylus: '>=0.54.8' 734 + sugarss: ^5.0.0 735 + terser: ^5.16.0 736 + tsx: ^4.8.1 737 + yaml: ^2.4.2 738 + peerDependenciesMeta: 739 + '@types/node': 740 + optional: true 741 + jiti: 742 + optional: true 743 + less: 744 + optional: true 745 + lightningcss: 746 + optional: true 747 + sass: 748 + optional: true 749 + sass-embedded: 750 + optional: true 751 + stylus: 752 + optional: true 753 + sugarss: 754 + optional: true 755 + terser: 756 + optional: true 757 + tsx: 758 + optional: true 759 + yaml: 760 + optional: true 761 + 762 + vite@8.0.3: 763 + resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} 764 + engines: {node: ^20.19.0 || >=22.12.0} 765 + hasBin: true 766 + peerDependencies: 767 + '@types/node': ^20.19.0 || >=22.12.0 768 + '@vitejs/devtools': ^0.1.0 769 + esbuild: ^0.27.0 770 + jiti: '>=1.21.0' 771 + less: ^4.0.0 772 + sass: ^1.70.0 773 + sass-embedded: ^1.70.0 774 + stylus: '>=0.54.8' 775 + sugarss: ^5.0.0 776 + terser: ^5.16.0 777 + tsx: ^4.8.1 778 + yaml: ^2.4.2 779 + peerDependenciesMeta: 780 + '@types/node': 781 + optional: true 782 + '@vitejs/devtools': 783 + optional: true 784 + esbuild: 785 + optional: true 786 + jiti: 787 + optional: true 788 + less: 789 + optional: true 790 + sass: 791 + optional: true 792 + sass-embedded: 793 + optional: true 794 + stylus: 795 + optional: true 796 + sugarss: 797 + optional: true 798 + terser: 799 + optional: true 800 + tsx: 801 + optional: true 802 + yaml: 803 + optional: true 804 + 805 + snapshots: 806 + 807 + '@emnapi/core@1.9.1': 808 + dependencies: 809 + '@emnapi/wasi-threads': 1.2.0 810 + tslib: 2.8.1 811 + optional: true 812 + 813 + '@emnapi/runtime@1.9.1': 814 + dependencies: 815 + tslib: 2.8.1 816 + optional: true 817 + 818 + '@emnapi/wasi-threads@1.2.0': 819 + dependencies: 820 + tslib: 2.8.1 821 + optional: true 822 + 823 + '@esbuild/aix-ppc64@0.27.4': 824 + optional: true 825 + 826 + '@esbuild/android-arm64@0.27.4': 827 + optional: true 828 + 829 + '@esbuild/android-arm@0.27.4': 830 + optional: true 831 + 832 + '@esbuild/android-x64@0.27.4': 833 + optional: true 834 + 835 + '@esbuild/darwin-arm64@0.27.4': 836 + optional: true 837 + 838 + '@esbuild/darwin-x64@0.27.4': 839 + optional: true 840 + 841 + '@esbuild/freebsd-arm64@0.27.4': 842 + optional: true 843 + 844 + '@esbuild/freebsd-x64@0.27.4': 845 + optional: true 846 + 847 + '@esbuild/linux-arm64@0.27.4': 848 + optional: true 849 + 850 + '@esbuild/linux-arm@0.27.4': 851 + optional: true 852 + 853 + '@esbuild/linux-ia32@0.27.4': 854 + optional: true 855 + 856 + '@esbuild/linux-loong64@0.27.4': 857 + optional: true 858 + 859 + '@esbuild/linux-mips64el@0.27.4': 860 + optional: true 861 + 862 + '@esbuild/linux-ppc64@0.27.4': 863 + optional: true 864 + 865 + '@esbuild/linux-riscv64@0.27.4': 866 + optional: true 867 + 868 + '@esbuild/linux-s390x@0.27.4': 869 + optional: true 870 + 871 + '@esbuild/linux-x64@0.27.4': 872 + optional: true 873 + 874 + '@esbuild/netbsd-arm64@0.27.4': 875 + optional: true 876 + 877 + '@esbuild/netbsd-x64@0.27.4': 878 + optional: true 879 + 880 + '@esbuild/openbsd-arm64@0.27.4': 881 + optional: true 882 + 883 + '@esbuild/openbsd-x64@0.27.4': 884 + optional: true 885 + 886 + '@esbuild/openharmony-arm64@0.27.4': 887 + optional: true 888 + 889 + '@esbuild/sunos-x64@0.27.4': 890 + optional: true 891 + 892 + '@esbuild/win32-arm64@0.27.4': 893 + optional: true 894 + 895 + '@esbuild/win32-ia32@0.27.4': 896 + optional: true 897 + 898 + '@esbuild/win32-x64@0.27.4': 899 + optional: true 900 + 901 + '@jridgewell/gen-mapping@0.3.13': 902 + dependencies: 903 + '@jridgewell/sourcemap-codec': 1.5.5 904 + '@jridgewell/trace-mapping': 0.3.31 905 + 906 + '@jridgewell/remapping@2.3.5': 907 + dependencies: 908 + '@jridgewell/gen-mapping': 0.3.13 909 + '@jridgewell/trace-mapping': 0.3.31 910 + 911 + '@jridgewell/resolve-uri@3.1.2': {} 912 + 913 + '@jridgewell/sourcemap-codec@1.5.5': {} 914 + 915 + '@jridgewell/trace-mapping@0.3.31': 916 + dependencies: 917 + '@jridgewell/resolve-uri': 3.1.2 918 + '@jridgewell/sourcemap-codec': 1.5.5 919 + 920 + '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': 921 + dependencies: 922 + '@emnapi/core': 1.9.1 923 + '@emnapi/runtime': 1.9.1 924 + '@tybys/wasm-util': 0.10.1 925 + optional: true 926 + 927 + '@oxc-project/types@0.122.0': {} 928 + 929 + '@rolldown/binding-android-arm64@1.0.0-rc.12': 930 + optional: true 931 + 932 + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': 933 + optional: true 934 + 935 + '@rolldown/binding-darwin-x64@1.0.0-rc.12': 936 + optional: true 937 + 938 + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': 939 + optional: true 940 + 941 + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': 942 + optional: true 943 + 944 + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': 945 + optional: true 946 + 947 + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': 948 + optional: true 949 + 950 + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': 951 + optional: true 952 + 953 + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': 954 + optional: true 955 + 956 + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': 957 + optional: true 958 + 959 + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': 960 + optional: true 961 + 962 + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': 963 + optional: true 964 + 965 + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': 966 + dependencies: 967 + '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) 968 + transitivePeerDependencies: 969 + - '@emnapi/core' 970 + - '@emnapi/runtime' 971 + optional: true 972 + 973 + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': 974 + optional: true 975 + 976 + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': 977 + optional: true 978 + 979 + '@rolldown/pluginutils@1.0.0-rc.12': {} 980 + 981 + '@rollup/rollup-android-arm-eabi@4.60.1': 982 + optional: true 983 + 984 + '@rollup/rollup-android-arm64@4.60.1': 985 + optional: true 986 + 987 + '@rollup/rollup-darwin-arm64@4.60.1': 988 + optional: true 989 + 990 + '@rollup/rollup-darwin-x64@4.60.1': 991 + optional: true 992 + 993 + '@rollup/rollup-freebsd-arm64@4.60.1': 994 + optional: true 995 + 996 + '@rollup/rollup-freebsd-x64@4.60.1': 997 + optional: true 998 + 999 + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': 1000 + optional: true 1001 + 1002 + '@rollup/rollup-linux-arm-musleabihf@4.60.1': 1003 + optional: true 1004 + 1005 + '@rollup/rollup-linux-arm64-gnu@4.60.1': 1006 + optional: true 1007 + 1008 + '@rollup/rollup-linux-arm64-musl@4.60.1': 1009 + optional: true 1010 + 1011 + '@rollup/rollup-linux-loong64-gnu@4.60.1': 1012 + optional: true 1013 + 1014 + '@rollup/rollup-linux-loong64-musl@4.60.1': 1015 + optional: true 1016 + 1017 + '@rollup/rollup-linux-ppc64-gnu@4.60.1': 1018 + optional: true 1019 + 1020 + '@rollup/rollup-linux-ppc64-musl@4.60.1': 1021 + optional: true 1022 + 1023 + '@rollup/rollup-linux-riscv64-gnu@4.60.1': 1024 + optional: true 1025 + 1026 + '@rollup/rollup-linux-riscv64-musl@4.60.1': 1027 + optional: true 1028 + 1029 + '@rollup/rollup-linux-s390x-gnu@4.60.1': 1030 + optional: true 1031 + 1032 + '@rollup/rollup-linux-x64-gnu@4.60.1': 1033 + optional: true 1034 + 1035 + '@rollup/rollup-linux-x64-musl@4.60.1': 1036 + optional: true 1037 + 1038 + '@rollup/rollup-openbsd-x64@4.60.1': 1039 + optional: true 1040 + 1041 + '@rollup/rollup-openharmony-arm64@4.60.1': 1042 + optional: true 1043 + 1044 + '@rollup/rollup-win32-arm64-msvc@4.60.1': 1045 + optional: true 1046 + 1047 + '@rollup/rollup-win32-ia32-msvc@4.60.1': 1048 + optional: true 1049 + 1050 + '@rollup/rollup-win32-x64-gnu@4.60.1': 1051 + optional: true 1052 + 1053 + '@rollup/rollup-win32-x64-msvc@4.60.1': 1054 + optional: true 1055 + 1056 + '@tailwindcss/node@4.2.2': 1057 + dependencies: 1058 + '@jridgewell/remapping': 2.3.5 1059 + enhanced-resolve: 5.20.1 1060 + jiti: 2.6.1 1061 + lightningcss: 1.32.0 1062 + magic-string: 0.30.21 1063 + source-map-js: 1.2.1 1064 + tailwindcss: 4.2.2 1065 + 1066 + '@tailwindcss/oxide-android-arm64@4.2.2': 1067 + optional: true 1068 + 1069 + '@tailwindcss/oxide-darwin-arm64@4.2.2': 1070 + optional: true 1071 + 1072 + '@tailwindcss/oxide-darwin-x64@4.2.2': 1073 + optional: true 1074 + 1075 + '@tailwindcss/oxide-freebsd-x64@4.2.2': 1076 + optional: true 1077 + 1078 + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': 1079 + optional: true 1080 + 1081 + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': 1082 + optional: true 1083 + 1084 + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': 1085 + optional: true 1086 + 1087 + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': 1088 + optional: true 1089 + 1090 + '@tailwindcss/oxide-linux-x64-musl@4.2.2': 1091 + optional: true 1092 + 1093 + '@tailwindcss/oxide-wasm32-wasi@4.2.2': 1094 + optional: true 1095 + 1096 + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': 1097 + optional: true 1098 + 1099 + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': 1100 + optional: true 1101 + 1102 + '@tailwindcss/oxide@4.2.2': 1103 + optionalDependencies: 1104 + '@tailwindcss/oxide-android-arm64': 4.2.2 1105 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 1106 + '@tailwindcss/oxide-darwin-x64': 4.2.2 1107 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 1108 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 1109 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 1110 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 1111 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 1112 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 1113 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 1114 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 1115 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 1116 + 1117 + '@tailwindcss/vite@4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(esbuild@0.27.4)(jiti@2.6.1))': 1118 + dependencies: 1119 + '@tailwindcss/node': 4.2.2 1120 + '@tailwindcss/oxide': 4.2.2 1121 + tailwindcss: 4.2.2 1122 + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(esbuild@0.27.4)(jiti@2.6.1) 1123 + 1124 + '@tybys/wasm-util@0.10.1': 1125 + dependencies: 1126 + tslib: 2.8.1 1127 + optional: true 1128 + 1129 + '@types/estree@1.0.8': {} 1130 + 1131 + detect-libc@2.1.2: {} 1132 + 1133 + enhanced-resolve@5.20.1: 1134 + dependencies: 1135 + graceful-fs: 4.2.11 1136 + tapable: 2.3.2 1137 + 1138 + esbuild@0.27.4: 1139 + optionalDependencies: 1140 + '@esbuild/aix-ppc64': 0.27.4 1141 + '@esbuild/android-arm': 0.27.4 1142 + '@esbuild/android-arm64': 0.27.4 1143 + '@esbuild/android-x64': 0.27.4 1144 + '@esbuild/darwin-arm64': 0.27.4 1145 + '@esbuild/darwin-x64': 0.27.4 1146 + '@esbuild/freebsd-arm64': 0.27.4 1147 + '@esbuild/freebsd-x64': 0.27.4 1148 + '@esbuild/linux-arm': 0.27.4 1149 + '@esbuild/linux-arm64': 0.27.4 1150 + '@esbuild/linux-ia32': 0.27.4 1151 + '@esbuild/linux-loong64': 0.27.4 1152 + '@esbuild/linux-mips64el': 0.27.4 1153 + '@esbuild/linux-ppc64': 0.27.4 1154 + '@esbuild/linux-riscv64': 0.27.4 1155 + '@esbuild/linux-s390x': 0.27.4 1156 + '@esbuild/linux-x64': 0.27.4 1157 + '@esbuild/netbsd-arm64': 0.27.4 1158 + '@esbuild/netbsd-x64': 0.27.4 1159 + '@esbuild/openbsd-arm64': 0.27.4 1160 + '@esbuild/openbsd-x64': 0.27.4 1161 + '@esbuild/openharmony-arm64': 0.27.4 1162 + '@esbuild/sunos-x64': 0.27.4 1163 + '@esbuild/win32-arm64': 0.27.4 1164 + '@esbuild/win32-ia32': 0.27.4 1165 + '@esbuild/win32-x64': 0.27.4 1166 + 1167 + fdir@6.5.0(picomatch@4.0.4): 1168 + optionalDependencies: 1169 + picomatch: 4.0.4 1170 + 1171 + fsevents@2.3.3: 1172 + optional: true 1173 + 1174 + graceful-fs@4.2.11: {} 1175 + 1176 + jiti@2.6.1: {} 1177 + 1178 + lightningcss-android-arm64@1.32.0: 1179 + optional: true 1180 + 1181 + lightningcss-darwin-arm64@1.32.0: 1182 + optional: true 1183 + 1184 + lightningcss-darwin-x64@1.32.0: 1185 + optional: true 1186 + 1187 + lightningcss-freebsd-x64@1.32.0: 1188 + optional: true 1189 + 1190 + lightningcss-linux-arm-gnueabihf@1.32.0: 1191 + optional: true 1192 + 1193 + lightningcss-linux-arm64-gnu@1.32.0: 1194 + optional: true 1195 + 1196 + lightningcss-linux-arm64-musl@1.32.0: 1197 + optional: true 1198 + 1199 + lightningcss-linux-x64-gnu@1.32.0: 1200 + optional: true 1201 + 1202 + lightningcss-linux-x64-musl@1.32.0: 1203 + optional: true 1204 + 1205 + lightningcss-win32-arm64-msvc@1.32.0: 1206 + optional: true 1207 + 1208 + lightningcss-win32-x64-msvc@1.32.0: 1209 + optional: true 1210 + 1211 + lightningcss@1.32.0: 1212 + dependencies: 1213 + detect-libc: 2.1.2 1214 + optionalDependencies: 1215 + lightningcss-android-arm64: 1.32.0 1216 + lightningcss-darwin-arm64: 1.32.0 1217 + lightningcss-darwin-x64: 1.32.0 1218 + lightningcss-freebsd-x64: 1.32.0 1219 + lightningcss-linux-arm-gnueabihf: 1.32.0 1220 + lightningcss-linux-arm64-gnu: 1.32.0 1221 + lightningcss-linux-arm64-musl: 1.32.0 1222 + lightningcss-linux-x64-gnu: 1.32.0 1223 + lightningcss-linux-x64-musl: 1.32.0 1224 + lightningcss-win32-arm64-msvc: 1.32.0 1225 + lightningcss-win32-x64-msvc: 1.32.0 1226 + 1227 + magic-string@0.30.21: 1228 + dependencies: 1229 + '@jridgewell/sourcemap-codec': 1.5.5 1230 + 1231 + nanoid@3.3.11: {} 1232 + 1233 + picocolors@1.1.1: {} 1234 + 1235 + picomatch@4.0.4: {} 1236 + 1237 + postcss@8.5.8: 1238 + dependencies: 1239 + nanoid: 3.3.11 1240 + picocolors: 1.1.1 1241 + source-map-js: 1.2.1 1242 + 1243 + rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1): 1244 + dependencies: 1245 + '@oxc-project/types': 0.122.0 1246 + '@rolldown/pluginutils': 1.0.0-rc.12 1247 + optionalDependencies: 1248 + '@rolldown/binding-android-arm64': 1.0.0-rc.12 1249 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 1250 + '@rolldown/binding-darwin-x64': 1.0.0-rc.12 1251 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 1252 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 1253 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 1254 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 1255 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 1256 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 1257 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 1258 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 1259 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 1260 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) 1261 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 1262 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 1263 + transitivePeerDependencies: 1264 + - '@emnapi/core' 1265 + - '@emnapi/runtime' 1266 + 1267 + rollup@4.60.1: 1268 + dependencies: 1269 + '@types/estree': 1.0.8 1270 + optionalDependencies: 1271 + '@rollup/rollup-android-arm-eabi': 4.60.1 1272 + '@rollup/rollup-android-arm64': 4.60.1 1273 + '@rollup/rollup-darwin-arm64': 4.60.1 1274 + '@rollup/rollup-darwin-x64': 4.60.1 1275 + '@rollup/rollup-freebsd-arm64': 4.60.1 1276 + '@rollup/rollup-freebsd-x64': 4.60.1 1277 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 1278 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 1279 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 1280 + '@rollup/rollup-linux-arm64-musl': 4.60.1 1281 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 1282 + '@rollup/rollup-linux-loong64-musl': 4.60.1 1283 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 1284 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 1285 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 1286 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 1287 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 1288 + '@rollup/rollup-linux-x64-gnu': 4.60.1 1289 + '@rollup/rollup-linux-x64-musl': 4.60.1 1290 + '@rollup/rollup-openbsd-x64': 4.60.1 1291 + '@rollup/rollup-openharmony-arm64': 4.60.1 1292 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 1293 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 1294 + '@rollup/rollup-win32-x64-gnu': 4.60.1 1295 + '@rollup/rollup-win32-x64-msvc': 4.60.1 1296 + fsevents: 2.3.3 1297 + 1298 + source-map-js@1.2.1: {} 1299 + 1300 + tailwindcss@4.2.2: {} 1301 + 1302 + tapable@2.3.2: {} 1303 + 1304 + tinyglobby@0.2.15: 1305 + dependencies: 1306 + fdir: 6.5.0(picomatch@4.0.4) 1307 + picomatch: 4.0.4 1308 + 1309 + toml@3.0.0: {} 1310 + 1311 + tslib@2.8.1: 1312 + optional: true 1313 + 1314 + typescript@5.9.3: {} 1315 + 1316 + vite-gleam@1.7.1(jiti@2.6.1)(lightningcss@1.32.0): 1317 + dependencies: 1318 + magic-string: 0.30.21 1319 + toml: 3.0.0 1320 + vite: 7.3.1(jiti@2.6.1)(lightningcss@1.32.0) 1321 + transitivePeerDependencies: 1322 + - '@types/node' 1323 + - jiti 1324 + - less 1325 + - lightningcss 1326 + - sass 1327 + - sass-embedded 1328 + - stylus 1329 + - sugarss 1330 + - terser 1331 + - tsx 1332 + - yaml 1333 + 1334 + vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0): 1335 + dependencies: 1336 + esbuild: 0.27.4 1337 + fdir: 6.5.0(picomatch@4.0.4) 1338 + picomatch: 4.0.4 1339 + postcss: 8.5.8 1340 + rollup: 4.60.1 1341 + tinyglobby: 0.2.15 1342 + optionalDependencies: 1343 + fsevents: 2.3.3 1344 + jiti: 2.6.1 1345 + lightningcss: 1.32.0 1346 + 1347 + vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(esbuild@0.27.4)(jiti@2.6.1): 1348 + dependencies: 1349 + lightningcss: 1.32.0 1350 + picomatch: 4.0.4 1351 + postcss: 8.5.8 1352 + rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) 1353 + tinyglobby: 0.2.15 1354 + optionalDependencies: 1355 + esbuild: 0.27.4 1356 + fsevents: 2.3.3 1357 + jiti: 2.6.1 1358 + transitivePeerDependencies: 1359 + - '@emnapi/core' 1360 + - '@emnapi/runtime'
+1
src/bsky_comments_widget.css
··· 1 + @import "tailwindcss";
+46
src/bsky_comments_widget.gleam
··· 1 + import lustre 2 + import lustre/attribute 3 + import lustre/element.{type Element} 4 + import lustre/element/html 5 + import widget 6 + 7 + pub fn main() { 8 + let app = lustre.simple(init, update, view) 9 + 10 + let assert Ok(_) = widget.register() 11 + let assert Ok(_) = lustre.start(app, "#app", Nil) 12 + 13 + Nil 14 + } 15 + 16 + type Model = 17 + Nil 18 + 19 + fn init(_) -> Model { 20 + Nil 21 + } 22 + 23 + type Msg = 24 + Nil 25 + 26 + fn update(_, _) -> Model { 27 + Nil 28 + } 29 + 30 + fn view(_) -> Element(Msg) { 31 + html.html([], [ 32 + html.head([], [ 33 + html.link([ 34 + attribute.rel("stylesheet"), 35 + attribute.href("bsky_comments_widget.css"), 36 + ]), 37 + ]), 38 + html.div([attribute.class("p-32 mx-auto w-full max-w-4xl")], [ 39 + widget.element([ 40 + widget.post_url( 41 + "https://bsky.app/profile/mrgan.com/post/3michx3lqa22y", 42 + ), 43 + ]), 44 + ]), 45 + ]) 46 + }
+27
src/ffi/datetime.mjs
··· 1 + export function getReadableDate(datestring) { 2 + const date = new Date(datestring) 3 + 4 + if (isNaN(date.getTime())) { 5 + return "Invalid Date" 6 + } 7 + 8 + const timeOptions = { 9 + hour: 'numeric', 10 + minute: '2-digit', 11 + hour12: true 12 + } 13 + 14 + const dateOptions = { 15 + month: 'short', 16 + day: 'numeric', 17 + year: 'numeric' 18 + } 19 + 20 + const timeFormatter = new Intl.DateTimeFormat(undefined, timeOptions) 21 + const dateFormatter = new Intl.DateTimeFormat(undefined, dateOptions) 22 + 23 + const formattedTime = timeFormatter.format(date) 24 + const formattedDate = dateFormatter.format(date) 25 + 26 + return `${formattedTime} · ${formattedDate}` 27 + }
+5
src/ffi/ffi.gleam
··· 1 + @external(javascript, "./datetime.mjs", "getReadableDate") 2 + pub fn get_readable_date(date: String) -> String 3 + 4 + @external(javascript, "./styles.mjs", "getCss") 5 + pub fn get_css() -> String
+6
src/ffi/styles.mjs
··· 1 + // hacky relative path, since the path is resolved from `build/`. could be improved? 2 + import widgetStyles from "../../../../../src/bsky_comments_widget.css?inline" 3 + 4 + export function getCss() { 5 + return widgetStyles 6 + }
+3
src/main.ts
··· 1 + import { register } from "./widget.gleam" 2 + 3 + register()
+127
src/models.gleam
··· 1 + import gleam/dynamic/decode 2 + 3 + pub type Post { 4 + Post( 5 + author: Author, 6 + uri: String, 7 + text: String, 8 + embed: Embed, 9 + replies: List(Post), 10 + likes: Int, 11 + reply_count: Int, 12 + created_at: String, 13 + ) 14 + } 15 + 16 + pub type Author { 17 + Author(did: String, handle: String, display_name: String, avatar_url: String) 18 + } 19 + 20 + pub type Embed { 21 + Images(images: List(Image)) 22 + Video(thumb: String) 23 + External(description: String, thumb: String, title: String, uri: String) 24 + NoEmbed 25 + } 26 + 27 + pub type Image { 28 + Image(fullsize: String, alt: String, aspect_ratio: #(Int, Int)) 29 + } 30 + 31 + pub fn thread_response_decoder() -> decode.Decoder(Post) { 32 + use thread <- decode.field("thread", thread_view_decoder()) 33 + decode.success(thread) 34 + } 35 + 36 + fn thread_view_decoder() -> decode.Decoder(Post) { 37 + use post <- decode.field("post", post_content_decoder()) 38 + use replies <- decode.optional_field( 39 + "replies", 40 + [], 41 + decode.list(thread_view_decoder()), 42 + ) 43 + 44 + decode.success(Post( 45 + author: post.author, 46 + uri: post.uri, 47 + text: post.text, 48 + embed: post.embed, 49 + replies: replies, 50 + likes: post.likes, 51 + reply_count: post.reply_count, 52 + created_at: post.created_at, 53 + )) 54 + } 55 + 56 + fn post_content_decoder() -> decode.Decoder(Post) { 57 + use author <- decode.field("author", author_decoder()) 58 + use uri <- decode.field("uri", decode.string) 59 + use text <- decode.subfield(["record", "text"], decode.string) 60 + use embed <- decode.optional_field("embed", NoEmbed, embed_decoder()) 61 + use likes <- decode.field("likeCount", decode.int) 62 + use reply_count <- decode.field("replyCount", decode.int) 63 + use created_at <- decode.subfield(["record", "createdAt"], decode.string) 64 + 65 + // replies get populated by thread_view_decoder() 66 + decode.success(Post( 67 + author:, 68 + uri:, 69 + text:, 70 + embed:, 71 + replies: [], 72 + likes:, 73 + reply_count:, 74 + created_at:, 75 + )) 76 + } 77 + 78 + fn author_decoder() -> decode.Decoder(Author) { 79 + use did <- decode.field("did", decode.string) 80 + use handle <- decode.field("handle", decode.string) 81 + use display_name <- decode.field("displayName", decode.string) 82 + use avatar_url <- decode.field("avatar", decode.string) 83 + decode.success(Author(did:, handle:, display_name:, avatar_url:)) 84 + } 85 + 86 + fn embed_decoder() -> decode.Decoder(Embed) { 87 + use type_ <- decode.field("$type", decode.string) 88 + 89 + case type_ { 90 + "app.bsky.embed.images#view" -> { 91 + use images <- decode.field("images", decode.list(image_decoder())) 92 + decode.success(Images(images:)) 93 + } 94 + 95 + "app.bsky.embed.external#view" -> { 96 + use ext <- decode.field("external", { 97 + use description <- decode.field("description", decode.string) 98 + use thumb <- decode.field("thumb", decode.string) 99 + use title <- decode.field("title", decode.string) 100 + use uri <- decode.field("uri", decode.string) 101 + decode.success(#(description, thumb, title, uri)) 102 + }) 103 + let #(description, thumb, title, uri) = ext 104 + decode.success(External(description:, thumb:, title:, uri:)) 105 + } 106 + 107 + "app.bsky.embed.video#view" -> { 108 + use thumb <- decode.field("thumbnail", decode.string) 109 + 110 + decode.success(Video(thumb:)) 111 + } 112 + 113 + // embed type that's not implemented right now 114 + _ -> decode.success(NoEmbed) 115 + } 116 + } 117 + 118 + fn image_decoder() -> decode.Decoder(Image) { 119 + use fullsize <- decode.field("fullsize", decode.string) 120 + use alt <- decode.field("alt", decode.string) 121 + use aspect_ratio <- decode.field("aspectRatio", { 122 + use width <- decode.field("width", decode.int) 123 + use height <- decode.field("height", decode.int) 124 + decode.success(#(width, height)) 125 + }) 126 + decode.success(Image(fullsize:, alt:, aspect_ratio:)) 127 + }
+339
src/widget.gleam
··· 1 + import gleam/http/request 2 + import gleam/int 3 + import gleam/list 4 + import gleam/option.{Some} 5 + import gleam/regexp 6 + import gleam/string 7 + 8 + import lustre 9 + import lustre/attribute.{type Attribute} 10 + import lustre/component 11 + import lustre/effect.{type Effect} 12 + import lustre/element.{type Element} 13 + import lustre/element/html 14 + import rsvp 15 + 16 + import ffi/ffi 17 + import models.{type Post} 18 + 19 + type Msg { 20 + ApiReturnedPost(Result(models.Post, rsvp.Error)) 21 + ParentChangedPostUrl(String) 22 + } 23 + 24 + type Model { 25 + Loading 26 + Comments(main_post: Post) 27 + CouldntLoad 28 + } 29 + 30 + pub fn register() -> Result(Nil, lustre.Error) { 31 + let component = 32 + lustre.component(init, update, view, [ 33 + component.on_attribute_change("post_url", fn(url) { 34 + Ok(ParentChangedPostUrl(url)) 35 + }), 36 + ]) 37 + 38 + lustre.register(component, "bsky-comments") 39 + } 40 + 41 + pub fn element(attributes: List(Attribute(msg))) -> Element(msg) { 42 + element.element("bsky-comments", attributes, []) 43 + } 44 + 45 + pub fn post_url(post_url: String) -> Attribute(msg) { 46 + attribute.attribute("post_url", post_url) 47 + } 48 + 49 + fn init(_) -> #(Model, Effect(Msg)) { 50 + let model = Loading 51 + 52 + #(model, effect.none()) 53 + } 54 + 55 + fn update(_model: Model, msg: Msg) -> #(Model, Effect(Msg)) { 56 + case msg { 57 + ApiReturnedPost(Ok(post)) -> #(Comments(main_post: post), effect.none()) 58 + ApiReturnedPost(Error(_)) -> #(CouldntLoad, effect.none()) 59 + ParentChangedPostUrl(url) -> { 60 + case parse_post_url(url) { 61 + Ok(#(authority, post_id)) -> #( 62 + Loading, 63 + fetch_posts(on_response: ApiReturnedPost, authority:, post_id:), 64 + ) 65 + Error(_) -> #(CouldntLoad, effect.none()) 66 + } 67 + } 68 + } 69 + } 70 + 71 + fn view(model: Model) -> Element(Msg) { 72 + html.div([], [ 73 + html.style([], ffi.get_css()), 74 + ..case model { 75 + Loading -> [html.text("Loading comments...")] 76 + Comments(main_post:) -> [render_thread(main_post)] 77 + CouldntLoad -> [ 78 + html.text( 79 + "Couldn't load. Check if the URL is working correctly. If you're an user, please just ignore this.", 80 + ), 81 + ] 82 + } 83 + ]) 84 + } 85 + 86 + pub fn render_thread(thread: Post) { 87 + html.div( 88 + [ 89 + attribute.class( 90 + "thread-container rounded-lg border-3 border-solid border-blue-200 flex flex-col gap-4 p-4 w-full mx-auto", 91 + ), 92 + ], 93 + list.map(thread.replies, fn(reply) { 94 + html.div([attribute.class("reply-wrapper")], [ 95 + render_post(reply), 96 + ]) 97 + }), 98 + ) 99 + } 100 + 101 + pub fn render_post(post: Post) { 102 + html.div( 103 + [ 104 + attribute.class("post-card p-4 bg-white rounded-lg"), 105 + ], 106 + [ 107 + html.a( 108 + [ 109 + attribute.href("https://bsky.app/profile/" <> post.author.handle), 110 + attribute.class("author-container flex items-center w-fit mb-3"), 111 + ], 112 + [ 113 + html.img([ 114 + attribute.src(post.author.avatar_url), 115 + attribute.class("author-avatar size-10 rounded-full mr-3"), 116 + attribute.alt("Avatar of " <> post.author.display_name), 117 + ]), 118 + html.div([], [ 119 + html.p( 120 + [ 121 + attribute.class( 122 + "author-name font-semibold text-gray-800 normal-case", 123 + ), 124 + ], 125 + [html.text(post.author.display_name)], 126 + ), 127 + html.p( 128 + [ 129 + attribute.class( 130 + "author-handle text-sm text-gray-500 normal-case", 131 + ), 132 + ], 133 + [ 134 + html.text("@" <> post.author.handle), 135 + ], 136 + ), 137 + ]), 138 + ], 139 + ), 140 + 141 + html.p( 142 + [attribute.class("post-text text-gray-900 text-base mb-2 normal-case")], 143 + [ 144 + html.text(post.text), 145 + ], 146 + ), 147 + 148 + html.div([attribute.class("post-embeds my-3")], [ 149 + render_embed(post.embed, post_uri_to_url(post.uri)), 150 + ]), 151 + 152 + html.p([attribute.class("created-at text-gray-400 text-sm")], [ 153 + html.text(ffi.get_readable_date(post.created_at)), 154 + ]), 155 + 156 + html.div( 157 + [ 158 + attribute.class( 159 + "post-content-container flex justify-between px-4 mt-1", 160 + ), 161 + ], 162 + [ 163 + html.a( 164 + [ 165 + attribute.href(post_uri_to_url(post.uri)), 166 + attribute.target("_blank"), 167 + attribute.class("post-likes text-sm text-gray-600"), 168 + ], 169 + [ 170 + html.span([attribute.class("post-likes-heart text-red-400")], [ 171 + html.text("♥ "), 172 + ]), 173 + html.text(int.to_string(post.likes)), 174 + ], 175 + ), 176 + html.div([attribute.class("buttons-container flex gap-2 -mr-4")], [ 177 + html.p([attribute.class("post-replies text-sm text-gray-600")], [ 178 + html.text(post.reply_count |> int.to_string() <> " replies"), 179 + ]), 180 + html.a( 181 + [ 182 + attribute.href(post_uri_to_url(post.uri)), 183 + attribute.target("_blank"), 184 + attribute.class( 185 + "post-reply-button border border-solid px-2 text-sm text-gray-600 hover:bg-gray-100", 186 + ), 187 + ], 188 + [html.text("Reply")], 189 + ), 190 + ]), 191 + ], 192 + ), 193 + 194 + case post.replies { 195 + [_, ..] -> { 196 + html.div( 197 + [ 198 + attribute.class( 199 + "reply-section rounded-lg border-3 border-solid border-blue-200 mt-2", 200 + ), 201 + ], 202 + list.map(post.replies, render_post(_)) 203 + ) 204 + } 205 + [] -> element.none() 206 + }, 207 + ], 208 + ) 209 + } 210 + 211 + pub fn render_embed(embed: models.Embed, post_url: String) { 212 + case embed { 213 + models.Images(images) -> { 214 + html.div( 215 + [attribute.class("embed-images grid grid-cols-1 gap-2")], 216 + list.map(images, fn(image) { 217 + html.img([ 218 + attribute.src(image.fullsize), 219 + attribute.alt(image.alt), 220 + attribute.class("embed-image max-w-full h-auto rounded-lg"), 221 + ]) 222 + }), 223 + ) 224 + } 225 + models.External(description, thumb, title, uri) -> { 226 + html.a( 227 + [ 228 + attribute.href(uri), 229 + attribute.target("_blank"), 230 + attribute.class( 231 + "embed-external block p-3 border border-solid border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors duration-150", 232 + ), 233 + ], 234 + [ 235 + case thumb { 236 + "" -> element.none() 237 + _ -> render_external_thumbnail(thumb) 238 + }, 239 + html.p( 240 + [ 241 + attribute.class( 242 + "external-title font-semibold text-blue-600 hover:underline", 243 + ), 244 + ], 245 + [html.text(title)], 246 + ), 247 + html.p( 248 + [attribute.class("external-description text-sm text-gray-700")], 249 + [ 250 + html.text(description), 251 + ], 252 + ), 253 + html.p([attribute.class("external-uri text-xs text-gray-500 mt-1")], [ 254 + html.text(uri), 255 + ]), 256 + ], 257 + ) 258 + } 259 + models.Video(thumb:) -> 260 + html.a( 261 + [ 262 + attribute.href(post_url), 263 + attribute.target("_blank"), 264 + attribute.class( 265 + "embed-external block p-3 border border-solid border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors duration-200", 266 + ), 267 + ], 268 + [ 269 + render_external_thumbnail(thumb), 270 + html.p( 271 + [ 272 + attribute.class( 273 + "post-video-text text-center font-bold text-gray-600", 274 + ), 275 + ], 276 + [ 277 + html.text("Click to see video"), 278 + ], 279 + ), 280 + ], 281 + ) 282 + models.NoEmbed -> element.none() 283 + } 284 + } 285 + 286 + fn render_external_thumbnail(thumbnail_url: String) -> Element(a) { 287 + html.img([ 288 + attribute.src(thumbnail_url), 289 + attribute.class("external-image w-full h-auto mb-2 rounded-md"), 290 + ]) 291 + } 292 + 293 + fn fetch_posts( 294 + on_response handle_response: fn(Result(Post, rsvp.Error)) -> msg, 295 + authority authority: String, 296 + post_id post_id: String, 297 + ) -> Effect(msg) { 298 + let assert Ok(req) = 299 + request.to("https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread") 300 + let req = 301 + req 302 + |> request.set_query([ 303 + #("uri", get_at_uri(authority, post_id)), 304 + #("depth", "10"), 305 + ]) 306 + let handler = 307 + rsvp.expect_json(models.thread_response_decoder(), handle_response) 308 + 309 + rsvp.send(req, handler) 310 + } 311 + 312 + fn get_at_uri(authority: String, post_id: String) -> String { 313 + "at://" <> authority <> "/app.bsky.feed.post/" <> post_id 314 + } 315 + 316 + fn parse_post_url(post_url: String) { 317 + let assert Ok(re) = 318 + regexp.from_string( 319 + "^https:\\/\\/bsky\\.app\\/profile\\/([a-zA-Z0-9._-]+)\\/post\\/([a-zA-Z0-9]+)$", 320 + ) 321 + 322 + case regexp.scan(re, post_url) { 323 + [regexp.Match(_, submatches:)] -> 324 + case submatches { 325 + [Some(authority), Some(post_id)] -> Ok(#(authority, post_id)) 326 + _ -> Error("invalid url") 327 + } 328 + _ -> Error("invalid url") 329 + } 330 + } 331 + 332 + fn post_uri_to_url(post_uri: String) { 333 + case post_uri { 334 + "at://" <> uri -> 335 + "https://bsky.app/profile/" 336 + <> uri |> string.replace("app.bsky.feed.post", "post") 337 + _ -> "invalid_uri" 338 + } 339 + }
+13
test/bsky_comments_widget_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }
+26
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2023", 4 + "useDefineForClassFields": true, 5 + "module": "ESNext", 6 + "lib": ["ES2023", "DOM", "DOM.Iterable"], 7 + "types": ["vite/client"], 8 + "skipLibCheck": true, 9 + 10 + /* Bundler mode */ 11 + "moduleResolution": "bundler", 12 + "allowImportingTsExtensions": true, 13 + "verbatimModuleSyntax": true, 14 + "moduleDetection": "force", 15 + "noEmit": true, 16 + 17 + /* Linting */ 18 + "strict": true, 19 + "noUnusedLocals": true, 20 + "noUnusedParameters": true, 21 + "erasableSyntaxOnly": true, 22 + "noFallthroughCasesInSwitch": true, 23 + "noUncheckedSideEffectImports": true 24 + }, 25 + "include": ["src"] 26 + }
+16
vite.config.ts
··· 1 + import { defineConfig } from 'vite' 2 + import tailwindcss from '@tailwindcss/vite' 3 + import gleam from "vite-gleam"; 4 + 5 + export default defineConfig({ 6 + build: { 7 + lib: { 8 + entry: "./src/main.ts", 9 + name: "bsky_comments_widget", 10 + fileName: "bsky_comments_widget", 11 + }, 12 + }, 13 + plugins: [ 14 + tailwindcss(), gleam() 15 + ], 16 + })