zero-knowledge file sharing
13
fork

Configure Feed

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

add audio recording

Juliet 6e6701da 85947c5f

+341 -191
+27 -27
bun.lock
··· 5 5 "": { 6 6 "name": "drop", 7 7 "dependencies": { 8 - "hono": "^4.12.9", 8 + "hono": "latest", 9 9 }, 10 10 "devDependencies": { 11 - "@types/bun": "^1.3.11", 12 - "oxfmt": "^0.42.0", 11 + "@types/bun": "latest", 12 + "oxfmt": "latest", 13 13 }, 14 14 "peerDependencies": { 15 - "typescript": "^6.0.2", 15 + "typescript": "latest", 16 16 }, 17 17 }, 18 18 }, 19 19 "packages": { 20 - "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.42.0", "", { "os": "android", "cpu": "arm" }, "sha512-dsqPTYsozeokRjlrt/b4E7Pj0z3eS3Eg74TWQuuKbjY4VttBmA88rB7d50Xrd+TZ986qdXCNeZRPEzZHAe+jow=="], 20 + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.44.0", "", { "os": "android", "cpu": "arm" }, "sha512-5UvghMd9SA/yvKTWCAxMAPXS1d2i054UeOf4iFjZjfayTwCINcC3oaSXjtbZfCaEpxgJod7XiOjTtby5yEv/BQ=="], 21 21 22 - "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.42.0", "", { "os": "android", "cpu": "arm64" }, "sha512-t+aAjHxcr5eOBphFHdg1ouQU9qmZZoRxnX7UOJSaTwSoKsb6TYezNKO0YbWytGXCECObRqNcUxPoPr0KaraAIg=="], 22 + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.44.0", "", { "os": "android", "cpu": "arm64" }, "sha512-IVudM1BWfvrYO++Khtzr8q9n5Rxu7msUvoFMqzGJVdX7HfUXUDHwaH2zHZNB58svx2J56pmCUzophyaPFkcG/A=="], 23 23 24 - "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.42.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ulpSEYMKg61C5bRMZinFHrKJYRoKGVbvMEXA5zM1puX3O9T6Q4XXDbft20yrDijpYWeuG59z3Nabt+npeTsM1A=="], 24 + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.44.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eWCLAIKAHfx88EqEP1Ga2yz7qVcqDU5lemn4xck+07bH182hDdprOHjbogyk0In1Djys3T0/pO2JepFnRJ41Mg=="], 25 25 26 - "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.42.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ttxLKhQYPdFiM8I/Ri37cvqChE4Xa562nNOsZFcv1CKTVLeEozXjKuYClNvxkXmNlcF55nzM80P+CQkdFBu+uQ=="], 26 + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.44.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-eHTBznHLM49++dwz07MblQ2cOXyIgeedmE3Wgy4ptUESj38/qYZyRi1MPwC9olQJWssMeY6WI3UZ7YmU5ggvyQ=="], 27 27 28 - "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.42.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Og7QS3yI3tdIKYZ58SXik0rADxIk2jmd+/YvuHRyKULWpG4V2fR5V4hvKm624Mc0cQET35waPXiCQWvjQEjwYQ=="], 28 + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.44.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jLMmbj0u0Ft43QpkUVr/0v1ZfQCGWAvU+WznEHcN3wZC/q6ox7XeSJtk9P36CCpiDSUf3sGnzbIuG1KdEMEDJQ=="], 29 29 30 - "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.42.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jwLOw/3CW4H6Vxcry4/buQHk7zm9Ne2YsidzTL1kpiMe4qqrRCwev3dkyWe2YkFmP+iZCQ7zku4KwjcLRoh8ew=="], 30 + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.44.0", "", { "os": "linux", "cpu": "arm" }, "sha512-n+A/u/ByK1qV8FVGOwyaSpw5NPNl0qlZfgTBqHeGIqr8Qzq1tyWZ4lAaxPoe5mZqE3w88vn3+jZtMxriHPE7tg=="], 31 31 32 - "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.42.0", "", { "os": "linux", "cpu": "arm" }, "sha512-XwXu2vkMtiq2h7tfvN+WA/9/5/1IoGAVCFPiiQUvcAuG3efR97KNcRGM8BetmbYouFotQ2bDal3yyjUx6IPsTg=="], 32 + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.44.0", "", { "os": "linux", "cpu": "arm" }, "sha512-5eax+FkxyCqAi3Rw0mrZFr7+KTt/XweFsbALR+B5ljWBLBl8nHe4ADrUnb1gLEfQCJLl+Ca5FIVD4xEt95AwIw=="], 33 33 34 - "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.42.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ea7s/XUJoT7ENAtUQDudFe3nkSM3e3Qpz4nJFRdzO2wbgXEcjnchKLEsV3+t4ev3r8nWxIYr9NRjPWtnyIFJVA=="], 34 + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.44.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-58l8JaHxSGOmOMOG2CIrNsnkRJAj0YcHQCmvNACniOa/vd1iRHhlPajczegzS5jwMENlqgreyiTR9iNlke8qCw=="], 35 35 36 - "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.42.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-+JA0YMlSdDqmacygGi2REp57c3fN+tzARD8nwsukx9pkCHK+6DkbAA9ojS4lNKsiBjIW8WWa0pBrBWhdZEqfuw=="], 36 + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.44.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg=="], 37 37 38 - "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.42.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-VfnET0j4Y5mdfCzh5gBt0NK28lgn5DKx+8WgSMLYYeSooHhohdbzwAStLki9pNuGy51y4I7IoW8bqwAaCMiJQg=="], 38 + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.44.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A=="], 39 39 40 - "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.42.0", "", { "os": "linux", "cpu": "none" }, "sha512-gVlCbmBkB0fxBWbhBj9rcxezPydsQHf4MFKeHoTSPicOQ+8oGeTQgQ8EeesSybWeiFPVRx3bgdt4IJnH6nOjAA=="], 40 + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.44.0", "", { "os": "linux", "cpu": "none" }, "sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ=="], 41 41 42 - "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.42.0", "", { "os": "linux", "cpu": "none" }, "sha512-zN5OfstL0avgt/IgvRu0zjQzVh/EPkcLzs33E9LMAzpqlLWiPWeMDZyMGFlSRGOdDjuNmlZBCgj0pFnK5u32TQ=="], 42 + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.44.0", "", { "os": "linux", "cpu": "none" }, "sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw=="], 43 43 44 - "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.42.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-9X6+H2L0qMc2sCAgO9HS03bkGLMKvOFjmEdchaFlany3vNZOjnVui//D8k/xZAtQv2vaCs1reD5KAgPoIU4msA=="], 44 + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.44.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA=="], 45 45 46 - "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.42.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BajxJ6KQvMMdpXGPWhBGyjb2Jvx4uec0w+wi6TJZ6Tv7+MzPwe0pO8g5h1U0jyFgoaF7mDl6yKPW3ykWcbUJRw=="], 46 + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.44.0", "", { "os": "linux", "cpu": "x64" }, "sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA=="], 47 47 48 - "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.42.0", "", { "os": "linux", "cpu": "x64" }, "sha512-0wV284I6vc5f0AqAhgAbHU2935B4bVpncPoe5n/WzVZY/KnHgqxC8iSFGeSyLWEgstFboIcWkOPck7tqbdHkzA=="], 48 + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.44.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A=="], 49 49 50 - "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.42.0", "", { "os": "none", "cpu": "arm64" }, "sha512-p4BG6HpGnhfgHk1rzZfyR6zcWkE7iLrWxyehHfXUy4Qa5j3e0roglFOdP/Nj5cJJ58MA3isQ5dlfkW2nNEpolw=="], 50 + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.44.0", "", { "os": "none", "cpu": "arm64" }, "sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw=="], 51 51 52 - "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.42.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-mn//WV60A+IetORDxYieYGAoQso4KnVRRjORDewMcod4irlRe0OSC7YPhhwaexYNPQz/GCFk+v9iUcZ2W22yxQ=="], 52 + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.44.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-yWzB+oCpSnP/dmw85eFLAT5o35Ve5pkGS2uF/UCISpIwDqf1xa7OpmtomiqY/Vzg8VyvMbuf6vroF2khF/+1Vg=="], 53 53 54 - "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.42.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-3gWltUrvuz4LPJXWivoAxZ28Of2O4N7OGuM5/X3ubPXCEV8hmgECLZzjz7UYvSDUS3grfdccQwmjynm+51EFpw=="], 54 + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.44.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-TcWpo18xEIE3AmIG2kpr3kz5IEhQgnx0lazl2+8L+3eTopOAUevQcmlr4nhguImNWz0OMeOZrYZOhJNCf16nlQ=="], 55 55 56 - "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.42.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Wg4TMAfQRL9J9AZevJ/ZNy3uyyDztDYQtGr4P8UyyzIhLhFrdSmz1J/9JT+rv0fiCDLaFOBQnj3f3K3+a5PzDQ=="], 56 + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.44.0", "", { "os": "win32", "cpu": "x64" }, "sha512-oj8aLkPJZppIM4CMQNsyir9ybM1Xw/CfGPTSsTnzpVGyljgfbdP0EVUlURiGM0BDrmw5psQ6ArmGCcUY/yABaQ=="], 57 57 58 - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], 58 + "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], 59 59 60 60 "@types/node": ["@types/node@25.3.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="], 61 61 62 - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], 62 + "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], 63 63 64 - "hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="], 64 + "hono": ["hono@4.12.12", "", {}, "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q=="], 65 65 66 - "oxfmt": ["oxfmt@0.42.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.42.0", "@oxfmt/binding-android-arm64": "0.42.0", "@oxfmt/binding-darwin-arm64": "0.42.0", "@oxfmt/binding-darwin-x64": "0.42.0", "@oxfmt/binding-freebsd-x64": "0.42.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.42.0", "@oxfmt/binding-linux-arm-musleabihf": "0.42.0", "@oxfmt/binding-linux-arm64-gnu": "0.42.0", "@oxfmt/binding-linux-arm64-musl": "0.42.0", "@oxfmt/binding-linux-ppc64-gnu": "0.42.0", "@oxfmt/binding-linux-riscv64-gnu": "0.42.0", "@oxfmt/binding-linux-riscv64-musl": "0.42.0", "@oxfmt/binding-linux-s390x-gnu": "0.42.0", "@oxfmt/binding-linux-x64-gnu": "0.42.0", "@oxfmt/binding-linux-x64-musl": "0.42.0", "@oxfmt/binding-openharmony-arm64": "0.42.0", "@oxfmt/binding-win32-arm64-msvc": "0.42.0", "@oxfmt/binding-win32-ia32-msvc": "0.42.0", "@oxfmt/binding-win32-x64-msvc": "0.42.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-QhejGErLSMReNuZ6vxgFHDyGoPbjTRNi6uGHjy0cvIjOQFqD6xmr/T+3L41ixR3NIgzcNiJ6ylQKpvShTgDfqg=="], 66 + "oxfmt": ["oxfmt@0.44.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.44.0", "@oxfmt/binding-android-arm64": "0.44.0", "@oxfmt/binding-darwin-arm64": "0.44.0", "@oxfmt/binding-darwin-x64": "0.44.0", "@oxfmt/binding-freebsd-x64": "0.44.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.44.0", "@oxfmt/binding-linux-arm-musleabihf": "0.44.0", "@oxfmt/binding-linux-arm64-gnu": "0.44.0", "@oxfmt/binding-linux-arm64-musl": "0.44.0", "@oxfmt/binding-linux-ppc64-gnu": "0.44.0", "@oxfmt/binding-linux-riscv64-gnu": "0.44.0", "@oxfmt/binding-linux-riscv64-musl": "0.44.0", "@oxfmt/binding-linux-s390x-gnu": "0.44.0", "@oxfmt/binding-linux-x64-gnu": "0.44.0", "@oxfmt/binding-linux-x64-musl": "0.44.0", "@oxfmt/binding-openharmony-arm64": "0.44.0", "@oxfmt/binding-win32-arm64-msvc": "0.44.0", "@oxfmt/binding-win32-ia32-msvc": "0.44.0", "@oxfmt/binding-win32-x64-msvc": "0.44.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-lnncqvHewyRvaqdrnntVIrZV2tEddz8lbvPsQzG/zlkfvgZkwy0HP1p/2u1aCDToeg1jb9zBpbJdfkV73Itw+w=="], 67 67 68 68 "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], 69 69
+3 -3
package.json
··· 9 9 "start": "bun run install:all && bun run build && bun run src/index.ts" 10 10 }, 11 11 "dependencies": { 12 - "hono": "^4.12.9" 12 + "hono": "^4.12.12" 13 13 }, 14 14 "devDependencies": { 15 - "@types/bun": "^1.3.11", 16 - "oxfmt": "^0.42.0" 15 + "@types/bun": "^1.3.12", 16 + "oxfmt": "^0.44.0" 17 17 }, 18 18 "peerDependencies": { 19 19 "typescript": "^6.0.2"
+2 -8
src/db.ts
··· 28 28 burn_after_read: number; 29 29 }; 30 30 31 - const selectStmt = db.prepare<FileRow, [string]>( 32 - "SELECT * FROM files WHERE id = ?", 33 - ); 31 + const selectStmt = db.prepare<FileRow, [string]>("SELECT * FROM files WHERE id = ?"); 34 32 35 33 const deleteStmt = db.prepare<void, [string]>("DELETE FROM files WHERE id = ?"); 36 34 ··· 42 40 "DELETE FROM files WHERE expires_at <= ? RETURNING id", 43 41 ); 44 42 45 - export function createFile( 46 - id: string, 47 - expiresAt: number, 48 - burnAfterRead: boolean, 49 - ): void { 43 + export function createFile(id: string, expiresAt: number, burnAfterRead: boolean): void { 50 44 insertStmt.run(id, expiresAt, burnAfterRead ? 1 : 0); 51 45 } 52 46
+1 -4
src/file.ts
··· 40 40 const expiresInStr = typeof expiresIn === "string" ? expiresIn.trim() : ""; 41 41 const expiresInSec = expiresInStr ? parseDuration(expiresInStr) : undefined; 42 42 if (!expiresInSec) { 43 - return c.json( 44 - { error: "Invalid lifetime. Use a duration like 30m, 24h, 7d" }, 45 - 400, 46 - ); 43 + return c.json({ error: "Invalid lifetime. Use a duration like 30m, 24h, 7d" }, 400); 47 44 } 48 45 if (expiresInSec > MAX_TTL) { 49 46 return c.json({ error: "expiresIn exceeds maximum allowed TTL" }, 400);
+27 -25
web/bun.lock
··· 53 53 54 54 "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], 55 55 56 - "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], 56 + "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], 57 57 58 - "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], 58 + "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], 59 59 60 - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], 60 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], 61 61 62 62 "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 63 63 ··· 69 69 70 70 "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 71 71 72 - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], 72 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="], 73 73 74 - "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], 74 + "@oxc-project/types": ["@oxc-project/types@0.124.0", "", {}, "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg=="], 75 75 76 - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.11", "", { "os": "android", "cpu": "arm64" }, "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A=="], 76 + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.15", "", { "os": "android", "cpu": "arm64" }, "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA=="], 77 77 78 - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg=="], 78 + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg=="], 79 79 80 - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ=="], 80 + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw=="], 81 81 82 - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag=="], 82 + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw=="], 83 83 84 - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm" }, "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ=="], 84 + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm" }, "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA=="], 85 85 86 - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q=="], 86 + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w=="], 87 87 88 - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg=="], 88 + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ=="], 89 89 90 - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw=="], 90 + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ=="], 91 91 92 - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw=="], 92 + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "s390x" }, "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ=="], 93 93 94 - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "x64" }, "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA=="], 94 + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA=="], 95 95 96 - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.11", "", { "os": "linux", "cpu": "x64" }, "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg=="], 96 + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw=="], 97 97 98 - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.11", "", { "os": "none", "cpu": "arm64" }, "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g=="], 98 + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.15", "", { "os": "none", "cpu": "arm64" }, "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg=="], 99 99 100 - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.11", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw=="], 100 + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.15", "", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.3" }, "cpu": "none" }, "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q=="], 101 101 102 - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q=="], 102 + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA=="], 103 103 104 - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.11", "", { "os": "win32", "cpu": "x64" }, "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA=="], 104 + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "x64" }, "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g=="], 105 105 106 - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.11", "", {}, "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ=="], 106 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.15", "", {}, "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g=="], 107 107 108 108 "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], 109 109 ··· 231 231 232 232 "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 233 233 234 - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 234 + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], 235 235 236 236 "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], 237 237 238 - "rolldown": ["rolldown@1.0.0-rc.11", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.11" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.11", "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", "@rolldown/binding-darwin-x64": "1.0.0-rc.11", "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw=="], 238 + "rolldown": ["rolldown@1.0.0-rc.15", "", { "dependencies": { "@oxc-project/types": "=0.124.0", "@rolldown/pluginutils": "1.0.0-rc.15" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-x64": "1.0.0-rc.15", "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g=="], 239 239 240 240 "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 241 241 ··· 261 261 262 262 "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], 263 263 264 - "vite": ["vite@8.0.2", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.11", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA=="], 264 + "vite": ["vite@8.0.8", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw=="], 265 265 266 - "vite-plugin-solid": ["vite-plugin-solid@2.11.11", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-YMZCXsLw9kyuvQFEdwLP27fuTQJLmjNoHy90AOJnbRuJ6DwShUxKFo38gdFrWn9v11hnGicKCZEaeI/TFs6JKw=="], 266 + "vite-plugin-solid": ["vite-plugin-solid@2.11.12", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA=="], 267 267 268 268 "vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="], 269 269 ··· 282 282 "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 283 283 284 284 "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], 285 + 286 + "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 285 287 } 286 288 }
+1 -4
web/index.html
··· 6 6 <title>drop</title> 7 7 <link rel="icon" href="/favicon.svg" type="image/svg+xml" /> 8 8 <link rel="preconnect" href="https://fonts.bunny.net" /> 9 - <link 10 - href="https://fonts.bunny.net/css?family=bricolage-grotesque:400,500" 11 - rel="stylesheet" 12 - /> 9 + <link href="https://fonts.bunny.net/css?family=bricolage-grotesque:400,500" rel="stylesheet" /> 13 10 </head> 14 11 <body id="root" class="bg-bg"> 15 12 <script type="module" src="/src/index.tsx"></script>
+2 -2
web/package.json
··· 13 13 "@tailwindcss/vite": "^4.2.2", 14 14 "tailwindcss": "^4.2.2", 15 15 "typescript": "^6.0.2", 16 - "vite": "^8.0.2", 17 - "vite-plugin-solid": "^2.11.11" 16 + "vite": "^8.0.8", 17 + "vite-plugin-solid": "^2.11.12" 18 18 } 19 19 }
+8 -23
web/src/lib/crypto.ts
··· 7 7 const PADDING_BLOCK = 4096; 8 8 9 9 export async function generateKey() { 10 - const key = await crypto.subtle.generateKey( 11 - { name: "AES-GCM", length: 256 }, 12 - true, 13 - ["encrypt", "decrypt"], 14 - ); 10 + const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, [ 11 + "encrypt", 12 + "decrypt", 13 + ]); 15 14 const raw = await crypto.subtle.exportKey("raw", key); 16 15 return { 17 16 key, ··· 24 23 25 24 export async function importKey(encoded: string) { 26 25 const raw = Uint8Array.fromBase64(encoded, { alphabet: "base64url" }); 27 - return crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, [ 28 - "encrypt", 29 - "decrypt", 30 - ]); 26 + return crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]); 31 27 } 32 28 33 29 // Pack: [u16 filenameLen][u64 fileLen][filename][file][zero padding to 4K boundary] 34 30 // Then encrypt the whole thing as one AES-GCM ciphertext. 35 - export async function encrypt( 36 - fileName: string, 37 - fileBuffer: ArrayBuffer, 38 - key: CryptoKey, 39 - ) { 31 + export async function encrypt(fileName: string, fileBuffer: ArrayBuffer, key: CryptoKey) { 40 32 const nameBytes = new TextEncoder().encode(fileName); 41 33 if (nameBytes.length > 0xffff) throw new Error("Filename too long"); 42 34 ··· 64 56 } 65 57 66 58 // Decrypt and unpack — returns { fileName, fileData } 67 - export async function decrypt( 68 - ciphertext: Uint8Array<ArrayBuffer>, 69 - key: CryptoKey, 70 - ) { 71 - const plain = await crypto.subtle.decrypt( 72 - { name: "AES-GCM", iv: IV }, 73 - key, 74 - ciphertext, 75 - ); 59 + export async function decrypt(ciphertext: Uint8Array<ArrayBuffer>, key: CryptoKey) { 60 + const plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv: IV }, key, ciphertext); 76 61 77 62 const view = new DataView(plain); 78 63 const nameLen = view.getUint16(0, false);
+1 -4
web/src/lib/crypto.worker.ts
··· 11 11 self.postMessage({ ciphertext }, [ciphertext.buffer]); 12 12 } else if (type === "decrypt") { 13 13 const { ciphertext } = e.data; 14 - const { fileName, fileData } = await decrypt( 15 - new Uint8Array(ciphertext), 16 - key, 17 - ); 14 + const { fileName, fileData } = await decrypt(new Uint8Array(ciphertext), key); 18 15 self.postMessage({ fileName, fileData }, [fileData.buffer]); 19 16 } 20 17 } catch (err: any) {
+3 -14
web/src/lib/utils.ts
··· 1 - export const IMAGE_EXTS = new Set([ 2 - "png", 3 - "jpg", 4 - "jpeg", 5 - "gif", 6 - "webp", 7 - "ico", 8 - "bmp", 9 - "avif", 10 - ]); 1 + export const IMAGE_EXTS = new Set(["png", "jpg", "jpeg", "gif", "webp", "ico", "bmp", "avif"]); 11 2 12 3 export const TEXT_EXTS = new Set([ 13 4 "txt", ··· 87 78 const secs = unixSec - Math.floor(Date.now() / 1000); 88 79 if (secs < 60) return "expires in less than a minute"; 89 80 const mins = Math.floor(secs / 60); 90 - if (secs < 3600) 91 - return `expires in ${mins} ${mins === 1 ? "minute" : "minutes"}`; 81 + if (secs < 3600) return `expires in ${mins} ${mins === 1 ? "minute" : "minutes"}`; 92 82 const hours = Math.floor(secs / 3600); 93 - if (secs < 86400) 94 - return `expires in ${hours} ${hours === 1 ? "hour" : "hours"}`; 83 + if (secs < 86400) return `expires in ${hours} ${hours === 1 ? "hour" : "hours"}`; 95 84 const days = Math.floor(secs / 86400); 96 85 return `expires in ${days} ${days === 1 ? "day" : "days"}`; 97 86 }
+258 -55
web/src/pages/Upload.tsx
··· 1 - import { 2 - createSignal, 3 - Show, 4 - onMount, 5 - onCleanup, 6 - createMemo, 7 - createEffect, 8 - } from "solid-js"; 1 + import { createSignal, Show, onMount, onCleanup, createMemo, createEffect } from "solid-js"; 9 2 10 3 import { generateKey } from "../lib/crypto"; 11 4 import { btnClass, btnStyle, fadeIn } from "../lib/ui"; ··· 28 21 } 29 22 30 23 type Status = "idle" | "encrypting" | "uploading"; 31 - type View = "result" | "uploading" | "file" | "empty"; 24 + type View = "result" | "uploading" | "file" | "empty" | "recording"; 25 + 26 + const REC_MIMES: { mime: string; ext: string }[] = [ 27 + { mime: "audio/webm;codecs=opus", ext: "webm" }, 28 + { mime: "audio/webm", ext: "webm" }, 29 + { mime: "audio/mp4", ext: "mp4" }, 30 + { mime: "audio/ogg;codecs=opus", ext: "ogg" }, 31 + ]; 32 + function pickRecMime() { 33 + const MR = (window as any).MediaRecorder; 34 + if (!MR) return null; 35 + for (const m of REC_MIMES) { 36 + if (MR.isTypeSupported?.(m.mime)) return m; 37 + } 38 + return { mime: "", ext: "webm" }; 39 + } 40 + 41 + function formatTime(s: number) { 42 + const m = Math.floor(s / 60); 43 + const r = s % 60; 44 + return `${m}:${r.toString().padStart(2, "0")}`; 45 + } 32 46 33 47 export default function Upload() { 34 48 const [file, setFile] = createSignal<File | null>(null); ··· 43 57 const [maxTtl, setMaxTtl] = createSignal(""); 44 58 const [expiryValue, setExpiryValue] = createSignal(""); 45 59 const [loading, setLoading] = createSignal(true); 60 + const [recording, setRecording] = createSignal(false); 61 + const [recSeconds, setRecSeconds] = createSignal(0); 62 + const [recLevel, setRecLevel] = createSignal(0); 63 + const [previewUrl, setPreviewUrl] = createSignal(""); 64 + 65 + const previewKind = createMemo<"audio" | "video" | "image" | null>(() => { 66 + const f = file(); 67 + if (!f) return null; 68 + if (f.type.startsWith("audio/")) return "audio"; 69 + if (f.type.startsWith("video/")) return "video"; 70 + if (f.type.startsWith("image/")) return "image"; 71 + return null; 72 + }); 73 + 74 + createEffect(() => { 75 + const f = file(); 76 + const kind = previewKind(); 77 + if (f && kind) { 78 + const url = URL.createObjectURL(f); 79 + setPreviewUrl(url); 80 + onCleanup(() => URL.revokeObjectURL(url)); 81 + } else { 82 + setPreviewUrl(""); 83 + } 84 + }); 46 85 47 86 let fileInput!: HTMLInputElement; 48 87 let activeXhr: XMLHttpRequest | null = null; 49 88 let worker: Worker | null = null; 89 + let mediaRecorder: MediaRecorder | null = null; 90 + let mediaStream: MediaStream | null = null; 91 + let audioCtx: AudioContext | null = null; 92 + let analyser: AnalyserNode | null = null; 93 + let recTimer: number | null = null; 94 + let levelRaf: number | null = null; 50 95 51 96 const RADIUS = 130; 52 97 const CENTER = RADIUS + 10; ··· 63 108 for (let x = left; x < right; x += WAVE_LEN / 2) { 64 109 const cx = x + WAVE_LEN / 4; 65 110 const ex = x + WAVE_LEN / 2; 66 - const dir = 67 - ((x - left) / (WAVE_LEN / 2)) % 2 === 0 ? -WAVE_AMP : WAVE_AMP; 111 + const dir = ((x - left) / (WAVE_LEN / 2)) % 2 === 0 ? -WAVE_AMP : WAVE_AMP; 68 112 d += ` Q ${cx} ${topY + dir} ${ex} ${topY}`; 69 113 } 70 114 d += ` V ${bottom} Z`; ··· 90 134 const view = createMemo<View>(() => { 91 135 if (resultUrl()) return "result"; 92 136 if (status() !== "idle") return "uploading"; 137 + if (recording()) return "recording"; 93 138 if (file()) return "file"; 94 139 return "empty"; 95 140 }); 96 141 142 + const canRecord = !!pickRecMime() && !!navigator.mediaDevices?.getUserMedia; 143 + 144 + const stopRecStream = () => { 145 + if (recTimer !== null) { 146 + clearInterval(recTimer); 147 + recTimer = null; 148 + } 149 + if (levelRaf !== null) { 150 + cancelAnimationFrame(levelRaf); 151 + levelRaf = null; 152 + } 153 + mediaStream?.getTracks().forEach((t) => t.stop()); 154 + mediaStream = null; 155 + audioCtx?.close().catch(() => {}); 156 + audioCtx = null; 157 + analyser = null; 158 + setRecLevel(0); 159 + }; 160 + 161 + const startRecording = async () => { 162 + const picked = pickRecMime(); 163 + if (!picked) { 164 + setError("recording not supported in this browser"); 165 + return; 166 + } 167 + setError(""); 168 + try { 169 + mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true }); 170 + } catch { 171 + setError("microphone permission denied"); 172 + return; 173 + } 174 + 175 + try { 176 + audioCtx = new AudioContext(); 177 + const src = audioCtx.createMediaStreamSource(mediaStream); 178 + analyser = audioCtx.createAnalyser(); 179 + analyser.fftSize = 512; 180 + src.connect(analyser); 181 + const buf = new Uint8Array(analyser.frequencyBinCount); 182 + let smoothed = 0; 183 + const tick = () => { 184 + if (!analyser) return; 185 + analyser.getByteTimeDomainData(buf); 186 + let sum = 0; 187 + for (let i = 0; i < buf.length; i++) { 188 + const v = (buf[i] - 128) / 128; 189 + sum += v * v; 190 + } 191 + const target = Math.min(1, Math.sqrt(sum / buf.length) * 5); 192 + const k = target > smoothed ? 0.25 : 0.08; 193 + smoothed += (target - smoothed) * k; 194 + setRecLevel(smoothed); 195 + levelRaf = requestAnimationFrame(tick); 196 + }; 197 + tick(); 198 + } catch {} 199 + 200 + const chunks: BlobPart[] = []; 201 + const mr = new MediaRecorder(mediaStream, picked.mime ? { mimeType: picked.mime } : undefined); 202 + mediaRecorder = mr; 203 + mr.ondataavailable = (e) => { 204 + if (e.data.size > 0) chunks.push(e.data); 205 + }; 206 + mr.onstop = () => { 207 + const type = picked.mime.split(";")[0] || "audio/webm"; 208 + const blob = new Blob(chunks, { type }); 209 + const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); 210 + const f = new File([blob], `recording-${ts}.${picked.ext}`, { type }); 211 + setFile(f); 212 + stopRecStream(); 213 + setRecording(false); 214 + }; 215 + mr.start(); 216 + setRecSeconds(0); 217 + setRecording(true); 218 + recTimer = window.setInterval(() => setRecSeconds((s) => s + 1), 1000); 219 + }; 220 + 221 + const stopRecording = () => { 222 + mediaRecorder?.state === "recording" && mediaRecorder.stop(); 223 + }; 224 + 225 + const cancelRecording = () => { 226 + if (mediaRecorder && mediaRecorder.state === "recording") { 227 + mediaRecorder.onstop = null as any; 228 + mediaRecorder.stop(); 229 + } 230 + stopRecStream(); 231 + setRecording(false); 232 + setRecSeconds(0); 233 + }; 234 + 97 235 const handleDragOver = (e: DragEvent) => { 98 236 e.preventDefault(); 99 237 setDragging(true); 100 238 }; 101 239 102 240 const handleDragLeave = (e: DragEvent) => { 103 - if ( 104 - e.relatedTarget === null || 105 - !document.body.contains(e.relatedTarget as Node) 106 - ) { 241 + if (e.relatedTarget === null || !document.body.contains(e.relatedTarget as Node)) { 107 242 setDragging(false); 108 243 } 109 244 }; ··· 141 276 document.removeEventListener("dragleave", handleDragLeave); 142 277 document.removeEventListener("drop", handleDrop); 143 278 worker?.terminate(); 279 + cancelRecording(); 144 280 }); 145 281 146 282 const removeFile = () => { ··· 171 307 try { 172 308 const { encoded } = await generateKey(); 173 309 const buffer = await f.arrayBuffer(); 174 - const ciphertext = await new Promise<Uint8Array<ArrayBuffer>>( 175 - (resolve, reject) => { 176 - worker!.onmessage = (e) => { 177 - if (e.data.error) reject(new Error(e.data.error)); 178 - else resolve(e.data.ciphertext); 179 - }; 180 - worker!.postMessage( 181 - { 182 - type: "encrypt", 183 - fileName: f.name || "file", 184 - fileBuffer: buffer, 185 - keyEncoded: encoded, 186 - }, 187 - [buffer], 188 - ); 189 - }, 190 - ); 310 + const ciphertext = await new Promise<Uint8Array<ArrayBuffer>>((resolve, reject) => { 311 + worker!.onmessage = (e) => { 312 + if (e.data.error) reject(new Error(e.data.error)); 313 + else resolve(e.data.ciphertext); 314 + }; 315 + worker!.postMessage( 316 + { 317 + type: "encrypt", 318 + fileName: f.name || "file", 319 + fileBuffer: buffer, 320 + keyEncoded: encoded, 321 + }, 322 + [buffer], 323 + ); 324 + }); 191 325 192 326 const formData = new FormData(); 193 327 formData.append("file", new Blob([ciphertext])); ··· 244 378 }; 245 379 246 380 createEffect(() => { 247 - document.title = 248 - status() === "uploading" ? `${progress()}% — drop` : "drop"; 381 + document.title = status() === "uploading" ? `${progress()}% — drop` : "drop"; 249 382 }); 250 383 251 384 return ( 252 385 <> 253 386 <div 254 - class="group relative mx-auto flex aspect-square w-[80vw] max-w-125 items-center justify-center" 387 + class="group relative mx-auto flex aspect-square w-[90vw] max-w-125 items-center justify-center sm:w-[80vw]" 255 388 onClick={() => view() === "empty" && fileInput.click()} 256 389 > 257 - <svg 258 - viewBox={`0 0 ${SIZE} ${SIZE}`} 259 - class="absolute inset-0 h-full w-full" 260 - > 390 + <svg viewBox={`0 0 ${SIZE} ${SIZE}`} class="absolute inset-0 h-full w-full"> 261 391 <defs> 262 392 <clipPath id="circle-clip"> 263 393 <circle cx={CENTER} cy={CENTER} r={RADIUS} /> ··· 281 411 stroke-dasharray={dragging() ? "3 2" : "none"} 282 412 class="transition-all duration-200" 283 413 /> 414 + <Show when={view() === "recording"}> 415 + <circle 416 + cx={CENTER} 417 + cy={CENTER} 418 + r={RADIUS} 419 + fill="var(--color-accent)" 420 + fill-opacity={recLevel() * 0.22} 421 + stroke="var(--color-accent)" 422 + stroke-width="5" 423 + opacity={recLevel() > 0.05 ? Math.min(1, recLevel() * 1.5) : 0} 424 + style={{ 425 + transition: "opacity 120ms linear, fill-opacity 120ms linear", 426 + }} 427 + /> 428 + </Show> 284 429 {/* Upload liquid fill */} 285 430 <Show when={status() === "uploading"}> 286 431 <g clip-path="url(#circle-clip)"> ··· 303 448 <Show when={!loading()}> 304 449 <Show when={view() === "result"}> 305 450 <div class="flex flex-col items-center gap-3" style={fadeIn}> 306 - <span 307 - class="text-muted" 308 - style={{ "font-size": "clamp(0.75rem, 2vw, 1rem)" }} 309 - > 451 + <span class="text-muted" style={{ "font-size": "clamp(0.75rem, 2vw, 1rem)" }}> 310 452 expires in {expiryValue()} 311 453 </span> 312 454 <button class={btnClass} style={btnStyle} onClick={copyLink}> ··· 326 468 </span> 327 469 </Show> 328 470 <span class="text-muted text-[10px] sm:text-xs"> 329 - {status() === "encrypting" 330 - ? "encrypting\u2026" 331 - : "uploading\u2026"} 471 + {status() === "encrypting" ? "encrypting\u2026" : "uploading\u2026"} 332 472 </span> 333 473 <button 334 474 class={ghostClass} ··· 344 484 345 485 <Show when={view() === "file"}> 346 486 <div class="flex flex-col items-center gap-4" style={fadeIn}> 487 + <Show when={previewUrl() && previewKind() === "audio"}> 488 + <audio 489 + src={previewUrl()} 490 + controls 491 + onClick={(e) => e.stopPropagation()} 492 + style={{ "max-width": "min(80vw, 260px)", width: "260px" }} 493 + /> 494 + </Show> 495 + <Show when={previewUrl() && previewKind() === "image"}> 496 + <img 497 + src={previewUrl()} 498 + onClick={(e) => e.stopPropagation()} 499 + class="max-h-28 rounded object-contain sm:max-h-40" 500 + style={{ "max-width": "min(55vw, 260px)" }} 501 + /> 502 + </Show> 503 + <Show when={previewUrl() && previewKind() === "video"}> 504 + <video 505 + src={previewUrl()} 506 + controls 507 + onClick={(e) => e.stopPropagation()} 508 + class="max-h-28 rounded sm:max-h-40" 509 + style={{ "max-width": "min(55vw, 260px)" }} 510 + /> 511 + </Show> 347 512 <div class="flex flex-col items-center gap-2"> 348 513 <span 349 514 class="text-text flex gap-1.5 truncate" 350 515 style={{ "max-width": "clamp(120px, 40vw, 300px)" }} 351 516 > 352 - <span 353 - class="truncate" 354 - style={{ "font-size": "clamp(0.75rem, 2vw, 1rem)" }} 355 - > 517 + <span class="truncate" style={{ "font-size": "clamp(0.75rem, 2vw, 1rem)" }}> 356 518 {file()!.name} 357 519 </span> 358 520 <span ··· 384 546 class={`flex size-4 items-center justify-center rounded border transition-colors ${burn() ? "bg-accent border-accent" : "bg-surface border-border"}`} 385 547 > 386 548 <Show when={burn()}> 387 - <svg 388 - class="text-bg h-3 w-3" 389 - viewBox="0 0 12 12" 390 - fill="none" 391 - > 549 + <svg class="text-bg h-3 w-3" viewBox="0 0 12 12" fill="none"> 392 550 <path 393 551 d="M2 6l3 3 5-5" 394 552 stroke="currentColor" ··· 417 575 </div> 418 576 </Show> 419 577 578 + <Show when={view() === "recording"}> 579 + <div class="flex flex-col items-center gap-3" style={fadeIn}> 580 + <span 581 + class="text-accent font-medium tabular-nums" 582 + style={{ "font-size": "clamp(1.5rem, 5vw, 2.5rem)" }} 583 + > 584 + {formatTime(recSeconds())} 585 + </span> 586 + <span class="text-muted text-[10px] sm:text-xs">recording…</span> 587 + <button 588 + class={btnClass} 589 + style={btnStyle} 590 + onClick={(e) => { 591 + e.stopPropagation(); 592 + stopRecording(); 593 + }} 594 + > 595 + stop 596 + </button> 597 + <button 598 + class={ghostClass} 599 + onClick={(e) => { 600 + e.stopPropagation(); 601 + cancelRecording(); 602 + }} 603 + > 604 + cancel 605 + </button> 606 + </div> 607 + </Show> 608 + 420 609 <Show when={view() === "empty"}> 421 610 <div class="flex flex-col items-center gap-3" style={fadeIn}> 422 611 <span ··· 435 624 > 436 625 browse 437 626 </button> 627 + <Show when={canRecord}> 628 + <button 629 + class="text-muted hover:text-accent-hover flex items-center gap-2 border-none bg-transparent py-1 transition-colors" 630 + style={{ "font-size": "clamp(0.75rem, 2vw, 1rem)" }} 631 + onClick={(e) => { 632 + e.stopPropagation(); 633 + startRecording(); 634 + }} 635 + aria-label="record audio" 636 + > 637 + <span class="bg-danger inline-block size-2.5 rounded-full" /> 638 + record 639 + </button> 640 + </Show> 438 641 <Show when={maxFileSize()}> 439 642 <span class="text-muted text-[10px] sm:text-xs"> 440 643 up to {formatBytes(maxFileSize())}
+8 -22
web/src/pages/View.tsx
··· 42 42 const [copied, setCopied] = createSignal(false); 43 43 44 44 let decryptedBlob: Blob | null = null; 45 - const worker = new Worker( 46 - new URL("../lib/crypto.worker.ts", import.meta.url), 47 - { 48 - type: "module", 49 - }, 50 - ); 45 + const worker = new Worker(new URL("../lib/crypto.worker.ts", import.meta.url), { 46 + type: "module", 47 + }); 51 48 52 49 onCleanup(() => worker.terminate()); 53 50 ··· 139 136 140 137 if (burnAfterRead()) setBurned(true); 141 138 142 - const mime = 143 - IMAGE_MIME[ext] || VIDEO_MIME[ext] || AUDIO_MIME[ext] || undefined; 139 + const mime = IMAGE_MIME[ext] || VIDEO_MIME[ext] || AUDIO_MIME[ext] || undefined; 144 140 if (mime) { 145 141 const blob = new Blob([fileData], { type: mime }); 146 142 decryptedBlob = blob; ··· 195 191 196 192 <Show when={stage() === "decrypting"}> 197 193 <div class="flex flex-col items-center gap-2" style={fadeIn}> 198 - <Show 199 - when={!decrypting()} 200 - fallback={<span class="text-muted text-xs">decrypting…</span>} 201 - > 194 + <Show when={!decrypting()} fallback={<span class="text-muted text-xs">decrypting…</span>}> 202 195 <span 203 196 class="text-accent font-medium tabular-nums" 204 197 style={{ "font-size": "clamp(1.5rem, 5vw, 2.5rem)" }} ··· 212 205 213 206 <Show when={stage() === "meta"}> 214 207 <div class="flex flex-col items-center gap-3" style={fadeIn}> 215 - <span 216 - class="text-muted" 217 - style={{ "font-size": "clamp(0.75rem, 2vw, 1rem)" }} 218 - > 208 + <span class="text-muted" style={{ "font-size": "clamp(0.75rem, 2vw, 1rem)" }}> 219 209 {formatBytes(size())} 220 210 </span> 221 211 <button class={btnClass} style={btnStyle} onClick={handleView}> 222 212 view 223 213 </button> 224 - <span class="text-muted text-xs"> 225 - {formatExpiry(expiresAt())} · burns after viewing 226 - </span> 214 + <span class="text-muted text-xs">{formatExpiry(expiresAt())} · burns after viewing</span> 227 215 </div> 228 216 </Show> 229 217 ··· 241 229 > 242 230 <span class="text-text flex min-w-0 gap-1.5"> 243 231 <span class="truncate">{fileName()}</span> 244 - <span class="text-muted shrink-0 font-medium"> 245 - {formatBytes(size())} 246 - </span> 232 + <span class="text-muted shrink-0 font-medium">{formatBytes(size())}</span> 247 233 </span> 248 234 <div class="flex shrink-0 items-center gap-2"> 249 235 <Show when={contentType() === "text"}>