diff --git a/package-lock.json b/package-lock.json index ec1cdcb..80954e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,12 @@ "name": "cmux-remote", "version": "0.1.0", "dependencies": { + "@ngrok/ngrok": "^1.7.0", "bcrypt": "^5.1.0", "commander": "^13.0.0", "express": "^5.0.0", "jsonwebtoken": "^9.0.0", "nanoid": "^5.0.0", - "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", "ws": "^8.18.0" }, @@ -25,7 +25,6 @@ "@types/express": "^5.0.0", "@types/jsonwebtoken": "^9.0.0", "@types/node": "^22.0.0", - "@types/qrcode": "^1.5.6", "@types/ws": "^8.5.0", "tsx": "^4.19.0", "typescript": "^5.7.0", @@ -501,6 +500,235 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@ngrok/ngrok": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.7.0.tgz", + "integrity": "sha512-P06o9TpxrJbiRbHQkiwy/rUrlXRupc+Z8KT4MiJfmcdWxvIdzjCaJOdnNkcOTs6DMyzIOefG5tvk/HLdtjqr0g==", + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@ngrok/ngrok-android-arm64": "1.7.0", + "@ngrok/ngrok-darwin-arm64": "1.7.0", + "@ngrok/ngrok-darwin-universal": "1.7.0", + "@ngrok/ngrok-darwin-x64": "1.7.0", + "@ngrok/ngrok-freebsd-x64": "1.7.0", + "@ngrok/ngrok-linux-arm-gnueabihf": "1.7.0", + "@ngrok/ngrok-linux-arm64-gnu": "1.7.0", + "@ngrok/ngrok-linux-arm64-musl": "1.7.0", + "@ngrok/ngrok-linux-x64-gnu": "1.7.0", + "@ngrok/ngrok-linux-x64-musl": "1.7.0", + "@ngrok/ngrok-win32-arm64-msvc": "1.7.0", + "@ngrok/ngrok-win32-ia32-msvc": "1.7.0", + "@ngrok/ngrok-win32-x64-msvc": "1.7.0" + } + }, + "node_modules/@ngrok/ngrok-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-android-arm64/-/ngrok-android-arm64-1.7.0.tgz", + "integrity": "sha512-8tco3ID6noSaNy+CMS7ewqPoIkIM6XO5COCzsUp3Wv3XEbMSyn65RN6cflX2JdqLfUCHcMyD0ahr9IEiHwqmbQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-arm64/-/ngrok-darwin-arm64-1.7.0.tgz", + "integrity": "sha512-+dmJSOzSO+MNDVrPOca2yYDP1W3KfP4qOlAkarIeFRIfqonQwq3QCBmcR7HAlZocLsSqEwyG6KP4RRvAuT0WGQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-universal": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-universal/-/ngrok-darwin-universal-1.7.0.tgz", + "integrity": "sha512-fDEfewyE2pWGFBhOSwQZObeHUkc65U1l+3HIgSOe094TMHsqmyJD0KTCgW9KSn0VP4OvDZbAISi1T3nvqgZYhQ==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-x64/-/ngrok-darwin-x64-1.7.0.tgz", + "integrity": "sha512-+fwMi5uHd9G8BS42MMa9ye6exI5lwTcjUO6Ut497Vu0qgLONdVRenRqnEePV+Q3KtQR7NjqkMnomVfkr9MBjtw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-freebsd-x64/-/ngrok-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-2OGgbrjy3yLRrqAz5N6hlUKIWIXSpR5RjQa2chtZMsSbszQ6c9dI+uVQfOKAeo05tHMUgrYAZ7FocC+ig0dzdQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm-gnueabihf/-/ngrok-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-SN9YIfEQiR9xN90QVNvdgvAemqMLoFVSeTWZs779145hQMhvF9Qd9rnWi6J+2uNNK10OczdV1oc/nq1es7u/3g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-gnu/-/ngrok-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-KDMgzPKFU2kbpVSaA2RZBBia5IPdJEe063YlyVFnSMJmPYWCUnMwdybBsucXfV9u1Lw/ZjKTKotIlbTWGn3HGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-musl/-/ngrok-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-e66vUdVrBlQ0lT9ZdamB4U604zt5Gualt8/WVcUGzbu8s5LajWd6g/mzZCUjK4UepjvMpfgmCp1/+rX7Rk8d5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-gnu/-/ngrok-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-M6gF0DyOEFqXLfWxObfL3bxYZ4+PnKBHuyLVaqNfFN9Y5utY2mdPOn5422Ppbk4XoIK5/YkuhRqPJl/9FivKEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-musl/-/ngrok-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-4Ijm0dKeoyzZTMaYxR2EiNjtlK81ebflg/WYIO1XtleFrVy4UJEGnxtxEidYoT4BfCqi4uvXiK2Mx216xXKvog==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-arm64-msvc/-/ngrok-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-u7qyWIJI2/YG1HTBnHwUR1+Z2tyGfAsUAItJK/+N1G0FeWJhIWQvSIFJHlaPy4oW1Dc8mSDBX9qvVsiQgLaRFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-ia32-msvc/-/ngrok-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-/UdYUsLNv/Q8j9YJsyIfq/jLCoD8WP+NidouucTUzSoDtmOsXBBT3itLrmPiZTEdEgKiFYLuC1Zon8XQQvbVLA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-x64-msvc/-/ngrok-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-UFJg/duEWzZlLkEs61Gz6/5nYhGaKI62I8dvUGdBR3NCtIMagehnFaFxmnXZldyHmCM8U0aCIFNpWRaKcrQkoA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -967,16 +1195,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/qrcode": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", - "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", @@ -1177,21 +1395,6 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/aproba": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", @@ -1330,15 +1533,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -1375,35 +1569,6 @@ "node": ">=10" } }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -1491,15 +1656,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -1534,12 +1690,6 @@ "node": ">=8" } }, - "node_modules/dijkstrajs": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", - "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", - "license": "MIT" - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1780,19 +1930,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1886,15 +2023,6 @@ "node": ">=10" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2148,18 +2276,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2485,42 +2601,6 @@ "wrappy": "1" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2530,15 +2610,6 @@ "node": ">= 0.8" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2595,15 +2666,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/postcss": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", @@ -2665,23 +2727,6 @@ "node": ">= 0.10" } }, - "node_modules/qrcode": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", - "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", - "license": "MIT", - "dependencies": { - "dijkstrajs": "^1.0.1", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - }, - "bin": { - "qrcode": "bin/qrcode" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/qrcode-terminal": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", @@ -2743,21 +2788,6 @@ "node": ">= 6" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "license": "ISC" - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -3466,12 +3496,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "license": "ISC" - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -3498,20 +3522,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3539,52 +3549,11 @@ } } }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "license": "ISC" - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } } } } diff --git a/package.json b/package.json index 02b9e83..ab43041 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,12 @@ "test:watch": "vitest" }, "dependencies": { + "@ngrok/ngrok": "^1.7.0", "bcrypt": "^5.1.0", "commander": "^13.0.0", "express": "^5.0.0", "jsonwebtoken": "^9.0.0", "nanoid": "^5.0.0", - "qrcode": "^1.5.4", "qrcode-terminal": "^0.12.0", "ws": "^8.18.0" }, @@ -29,7 +29,6 @@ "@types/express": "^5.0.0", "@types/jsonwebtoken": "^9.0.0", "@types/node": "^22.0.0", - "@types/qrcode": "^1.5.6", "@types/ws": "^8.5.0", "tsx": "^4.19.0", "typescript": "^5.7.0", diff --git a/public/css/claude.css b/public/css/claude.css new file mode 100644 index 0000000..05689d3 --- /dev/null +++ b/public/css/claude.css @@ -0,0 +1,407 @@ +/* Claude Code client view styles — scoped to body.claude-mode */ + +/* ── Terminal output override ── */ +body.claude-mode .terminal-output { + white-space: normal; + word-break: break-word; + padding: 10px 14px; +} + + +/* ── Scrollable container for wide content (code/diff/tool-result) ── */ +.cc-scrollable { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: thin; + scrollbar-color: var(--border-color) transparent; +} +.cc-scrollable::-webkit-scrollbar { height: 4px; } +.cc-scrollable::-webkit-scrollbar-track { background: transparent; } +.cc-scrollable::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 2px; +} + +/* ────────────────────────────────────── */ +/* Prompt block */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-prompt { + display: flex; + align-items: baseline; + gap: 8px; + padding: 8px 12px 6px; + margin: 4px -14px 0; + border-top: 1px solid var(--border-color); + border-radius: var(--radius-xs); + background: var(--accent-soft); + border-left: 3px solid var(--accent); + flex-wrap: wrap; +} + +body.claude-mode .cc-prompt.cc-prompt-active { + background: var(--bg-tertiary); + border-left-color: var(--green); + border-top-color: var(--border-subtle); +} + +body.claude-mode .cc-prompt-marker { + color: var(--green); + font-weight: 700; + font-size: 1.05em; + flex-shrink: 0; +} + +body.claude-mode .cc-prompt-text { + color: var(--text-primary); + white-space: pre-wrap; + word-break: break-word; + flex: 1; +} + +body.claude-mode .cc-prompt-cont { + width: 100%; + padding-left: 1.8em; + color: var(--text-secondary); + white-space: pre-wrap; + word-break: break-word; +} + +/* ────────────────────────────────────── */ +/* Tool-use block */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-tool-use { + margin: 3px 0; +} + +body.claude-mode .cc-tool-header { + display: flex; + align-items: baseline; + gap: 4px; + overflow: hidden; +} + +body.claude-mode .cc-tool-icon { + color: var(--blue); + flex-shrink: 0; +} + +body.claude-mode .cc-tool-name { + color: var(--blue); + font-weight: 600; + flex-shrink: 0; +} + +body.claude-mode .cc-tool-args { + color: var(--cyan); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + min-width: 0; +} + +body.claude-mode .cc-tool-body { + padding-left: 1.5em; + margin-top: 1px; +} + +body.claude-mode .cc-tool-body .cc-line { + white-space: pre-wrap; + word-break: break-word; + color: var(--text-secondary); + font-size: 0.9em; +} + +/* ────────────────────────────────────── */ +/* Tool-result block */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-tool-result { + border-left: 2px solid var(--blue); + margin: 4px 0; + padding-left: 8px; + opacity: 0.85; +} + +body.claude-mode .cc-tool-result .cc-scrollable pre { + margin: 0; + padding: 4px 0; + white-space: pre-wrap; + word-break: break-word; + color: var(--text-secondary); + font-size: 0.88em; + line-height: 1.5; +} + +/* ────────────────────────────────────── */ +/* Thinking block */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-thinking { + color: var(--purple); + font-style: italic; + opacity: 0.7; + white-space: pre-wrap; + word-break: break-word; + margin: 2px 0; + font-size: 0.9em; +} + +/* ────────────────────────────────────── */ +/* Response / Text blocks */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-response .cc-line, +body.claude-mode .cc-text .cc-line { + white-space: pre-wrap; + word-break: break-word; + line-height: 1.65; +} + +/* ────────────────────────────────────── */ +/* Code block */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-code { + margin: 6px 0; + border-radius: var(--radius-xs); + background: var(--bg-tertiary); + border: 1px solid var(--border-subtle); + overflow: hidden; +} + +body.claude-mode .cc-code-content { + margin: 0; + padding: 10px 14px; + white-space: pre; + word-break: normal; + font-size: 0.88em; + line-height: 1.5; + color: var(--text-primary); +} + +/* ────────────────────────────────────── */ +/* Table block */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-table-wrap { + margin: 6px 0; + border-radius: var(--radius-xs); + border: 1px solid var(--border-subtle); + overflow: hidden; +} + +body.claude-mode .cc-table { + border-collapse: collapse; + width: max-content; + min-width: 100%; + font-size: 0.82em; + line-height: 1.45; +} + +body.claude-mode .cc-table th, +body.claude-mode .cc-table td { + padding: 5px 10px; + border: 1px solid var(--border-subtle); + white-space: nowrap; + text-align: left; +} + +body.claude-mode .cc-table thead th { + background: var(--bg-tertiary); + font-weight: 600; + color: var(--text-primary); + border-bottom: 1px solid var(--border-color); +} + +body.claude-mode .cc-table tbody td { + color: var(--text-secondary); + background: var(--bg-primary); +} + +body.claude-mode .cc-table tbody tr:nth-child(even) td { + background: var(--bg-tertiary); +} + +/* ────────────────────────────────────── */ +/* Diff block */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-diff { + margin: 4px 0; + border-radius: var(--radius-xs); + background: var(--bg-tertiary); + border: 1px solid var(--border-subtle); + overflow: hidden; +} + +body.claude-mode .cc-diff-content { + padding: 6px 10px; + font-size: 0.88em; + line-height: 1.5; +} + +body.claude-mode .cc-diff-line { + display: block; + white-space: pre; + word-break: normal; +} + +body.claude-mode .cc-diff-line.cc-added { + color: var(--green); + background: rgba(52, 211, 153, 0.18); +} + +body.claude-mode .cc-diff-line.cc-removed { + color: var(--red); + background: rgba(248, 113, 113, 0.18); +} + +body.claude-mode .cc-diff-line.cc-hunk { + color: var(--cyan); + opacity: 0.75; +} + +/* ────────────────────────────────────── */ +/* Selection options */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-selection-options { + display: flex; + flex-direction: column; + gap: 6px; + padding: 8px 0; +} + +body.claude-mode .cc-option { + display: flex; + align-items: center; + gap: 10px; + padding: 13px 16px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: var(--bg-secondary); + color: var(--text-primary); + font-family: inherit; + font-size: 0.9em; + cursor: pointer; + text-align: left; + width: 100%; + -webkit-user-select: none; + user-select: none; + transition: background var(--transition-fast), border-color var(--transition-fast); +} + +body.claude-mode .cc-option:hover { + background: var(--bg-tertiary); + border-color: var(--accent); +} + +body.claude-mode .cc-option:active { + background: var(--accent-soft); + transform: scale(0.99); +} + +body.claude-mode .cc-option-index { + font-weight: 700; + color: var(--accent); + font-size: 0.85em; + min-width: 1.4em; + flex-shrink: 0; +} + +body.claude-mode .cc-option-label { + flex: 1; +} + +/* ────────────────────────────────────── */ +/* Empty spacer */ +/* ────────────────────────────────────── */ +body.claude-mode .cc-empty { + height: 0.5em; +} + +/* ────────────────────────────────────── */ +/* Status bar */ +/* ────────────────────────────────────── */ +.cc-status-bar { + position: sticky; + bottom: 0; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 14px; + background: var(--bg-secondary); + backdrop-filter: blur(var(--blur-amount)); + -webkit-backdrop-filter: blur(var(--blur-amount)); + border-top: 1px solid var(--border-color); + font-size: 0.76rem; + color: var(--text-secondary); + z-index: 10; + font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', monospace; +} + +.cc-mode-pill { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: var(--radius-pill); + font-weight: 600; + font-size: 0.72em; + cursor: pointer; + border: none; + transition: opacity var(--transition-fast); +} +.cc-mode-pill:active { opacity: 0.7; } + +.cc-mode-plan { background: rgba(251, 191, 36, 0.18); color: var(--yellow); } +.cc-mode-code { background: rgba(52, 211, 153, 0.18); color: var(--green); } +.cc-mode-bypass { background: rgba(248, 113, 113, 0.18); color: var(--red); } + +.cc-status-branch { + color: var(--text-dimmed); +} + +.cc-status-stats { + color: var(--text-dimmed); + margin-left: auto; +} + +/* ────────────────────────────────────── */ +/* Action bar (Claude keyboard row) */ +/* ────────────────────────────────────── */ +.cc-action-bar { + display: flex; + gap: 5px; + padding: 4px 0 0; +} + +.cc-action-btn { + flex: 1; + height: 36px; + border: 1px solid var(--key-border); + border-bottom: 2px solid var(--key-depth); + border-radius: var(--radius-xs); + background: var(--key-bg); + color: var(--text-primary); + font-family: inherit; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1px; + padding: 0 4px; + -webkit-user-select: none; + user-select: none; + transition: background var(--transition-fast), transform 80ms; +} + +.cc-action-btn:active { + background: var(--key-active-bg); + border-bottom-width: 1px; + transform: translateY(1px); +} + +.cc-action-icon { font-size: 0.9em; line-height: 1; } +.cc-action-label { font-size: 0.6rem; color: var(--text-dimmed); line-height: 1; } + +.cc-action-stop .cc-action-icon { color: var(--red); } + +/* Landscape adjustment */ +@media (max-height: 500px) { + .cc-action-btn { height: 30px; } +} diff --git a/public/css/keyboard.css b/public/css/keyboard.css index 585b136..47be917 100644 --- a/public/css/keyboard.css +++ b/public/css/keyboard.css @@ -4,14 +4,13 @@ backdrop-filter: blur(var(--blur-amount)); -webkit-backdrop-filter: blur(var(--blur-amount)); border-top: 1px solid var(--border-color); - padding: 8px 8px; - padding-bottom: max(8px, env(safe-area-inset-bottom)); + padding: 10px 10px 8px; } .key-row { display: flex; - gap: 5px; - margin-bottom: 5px; + gap: 6px; + margin-bottom: 7px; align-items: center; } @@ -22,15 +21,15 @@ /* ── Physical key style ──────────────────────────── */ .key { flex: 0 0 auto; - min-width: 40px; - height: 36px; + min-width: 44px; + height: 42px; border: 1px solid var(--key-border); border-bottom: 2px solid var(--key-depth); border-radius: var(--radius-xs); background: var(--key-bg); background-image: linear-gradient(180deg, var(--key-bg-highlight) 0%, transparent 60%); color: var(--text-primary); - font-size: 0.77rem; + font-size: 0.88rem; font-weight: 500; font-family: inherit; cursor: pointer; @@ -52,7 +51,7 @@ .key.modifier { font-weight: 600; - font-size: 0.7rem; + font-size: 0.8rem; letter-spacing: 0.03em; text-transform: uppercase; color: var(--text-secondary); @@ -78,10 +77,10 @@ .key.shortcut { font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', monospace; - font-size: 0.7rem; + font-size: 0.82rem; font-weight: 700; color: var(--text-secondary); - min-width: 36px; + min-width: 40px; } .key-spacer { @@ -95,13 +94,13 @@ .text-input { flex: 1; - height: 38px; + height: 44px; border: 1px solid var(--border-color); border-radius: var(--radius-pill); background: var(--bg-primary); color: var(--text-primary); - font-size: 0.9rem; - padding: 0 16px; + font-size: 1rem; + padding: 0 18px; outline: none; font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', monospace; transition: border-color var(--transition-fast), box-shadow var(--transition-fast); @@ -117,8 +116,8 @@ } .send-key { - width: 38px; - height: 38px; + width: 44px; + height: 44px; min-width: unset; padding: 0; flex-shrink: 0; @@ -143,18 +142,19 @@ /* ── Landscape ───────────────────────────────────── */ @media (max-height: 500px) { .key { - height: 30px; - min-width: 34px; - font-size: 0.68rem; + height: 32px; + min-width: 36px; + font-size: 0.75rem; } .text-input, .send-key { - height: 30px; + height: 34px; } .virtual-keyboard { - padding: 4px; + padding: 5px 6px 4px; } .key-row { - margin-bottom: 3px; + gap: 4px; + margin-bottom: 4px; } } @@ -162,7 +162,6 @@ @media (min-width: 1024px) { .virtual-keyboard { padding: 10px 20px; - padding-bottom: max(10px, env(safe-area-inset-bottom)); } .key { @@ -184,24 +183,53 @@ filter: brightness(1.12); box-shadow: 0 4px 16px var(--accent-glow); } + + .text-input { max-width: unset; } + .key-row.all-keys { overflow: visible; } } -.key-row.shortcut-keys { +.key-row.all-keys { overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; scrollbar-width: none; } -.key-row.shortcut-keys::-webkit-scrollbar { display: none; } -.key-row.shortcut-keys .key.shortcut { min-width: 32px; padding: 0 5px; } +.key-row.all-keys::-webkit-scrollbar { display: none; } +.key-row.all-keys .key.shortcut { min-width: 32px; padding: 0 5px; } .keyboard-toggle { - display: flex; align-items: center; justify-content: center; gap: 6px; - width: 100%; height: 28px; - background: var(--bg-secondary); border: none; - border-top: 1px solid var(--border-color); - color: var(--text-dimmed); font-size: 0.72rem; - cursor: pointer; flex-shrink: 0; + width: 44px; + height: 44px; + min-width: unset; + flex-shrink: 0; + padding: 0; + border: 1px solid var(--key-border); + border-bottom: 2px solid var(--key-depth); + border-radius: var(--radius-pill); + background: var(--key-bg); + background-image: linear-gradient(180deg, var(--key-bg-highlight) 0%, transparent 60%); + color: var(--text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + -webkit-user-select: none; + user-select: none; + transition: background var(--transition-fast), color var(--transition-fast); } -.keyboard-toggle:hover { color: var(--text-secondary); background: var(--bg-tertiary); } + +.keyboard-toggle:active { + background: var(--key-active-bg); + background-image: none; + border-bottom-width: 1px; + transform: translateY(1px); +} + +.virtual-keyboard.collapsed .keyboard-toggle { + color: var(--accent); + background: var(--accent-soft); + border-color: var(--accent); +} + .virtual-keyboard.collapsed .key-row:not(.input-row) { display: none; } +.virtual-keyboard.collapsed { padding-bottom: env(safe-area-inset-bottom, 8px); } \ No newline at end of file diff --git a/public/css/main.css b/public/css/main.css index e804076..01e57c7 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -3,12 +3,21 @@ -webkit-tap-highlight-color: transparent; } -html, body { +html { margin: 0; padding: 0; height: 100%; overflow: hidden; background: var(--bg-primary); +} + +body { + margin: 0; + padding: 0; + height: 100%; + height: -webkit-fill-available; + overflow: hidden; + background: var(--bg-primary); color: var(--text-primary); font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Inter', system-ui, sans-serif; -webkit-font-smoothing: antialiased; @@ -19,17 +28,32 @@ html, body { display: flex; flex-direction: column; height: 100%; - height: 100dvh; padding-top: env(safe-area-inset-top); - padding-bottom: env(safe-area-inset-bottom); + background: var(--bg-secondary); +} + +/* iOS PWA: window.innerHeight = Safari viewport (793px), screen.height = full screen (852px). + Use screen.height via --screen-h to fill the true physical height. */ +@media (max-width: 1023px) { + html { + background: var(--bg-secondary); + } + .app-container { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 100vh; + height: var(--screen-h, 100vh); + } } /* ── Top bar ─────────────────────────────────────── */ .top-bar { display: flex; align-items: center; - height: 48px; - padding: 0 10px; + height: 52px; + padding: 0 12px; background: var(--bg-secondary); backdrop-filter: blur(var(--blur-amount)); -webkit-backdrop-filter: blur(var(--blur-amount)); @@ -56,9 +80,9 @@ html, body { background: none; border: none; color: var(--text-secondary); - font-size: 1.15rem; - width: 34px; - height: 34px; + font-size: 1.25rem; + width: 38px; + height: 38px; padding: 0; cursor: pointer; border-radius: var(--radius-sm); @@ -76,10 +100,35 @@ html, body { .app-title { font-weight: 700; - font-size: 0.85rem; + font-size: 0.95rem; letter-spacing: -0.02em; color: var(--text-primary); white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + display: flex; + align-items: center; + gap: 7px; +} + +.title-status { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; + background: var(--text-dimmed); + transition: background 0.3s ease; +} + +.title-status.idle { + background: var(--yellow); + opacity: 0.7; +} + +.title-status.working { + background: var(--green); + animation: status-pulse 1.8s ease-in-out infinite; } .top-bar-right { @@ -89,39 +138,18 @@ html, body { gap: 10px; } -/* Connection status pill */ +/* Connection status dot */ .connection-status { - display: inline-flex; - align-items: center; - gap: 5px; - padding: 3px 8px 3px 6px; - border-radius: var(--radius-pill); - background: rgba(248, 113, 113, 0.08); - border: 1px solid rgba(248, 113, 113, 0.15); - font-size: 0.7rem; - font-weight: 600; - color: var(--red); - transition: all var(--transition-normal); - white-space: nowrap; -} - -.connection-status::before { - content: ''; - width: 5px; - height: 5px; + width: 7px; + height: 7px; border-radius: 50%; background: var(--red); - flex-shrink: 0; box-shadow: 0 0 5px var(--red); + flex-shrink: 0; + transition: background var(--transition-normal), box-shadow var(--transition-normal); } .connection-status.connected { - background: rgba(52, 211, 153, 0.08); - border-color: rgba(52, 211, 153, 0.15); - color: var(--green); -} - -.connection-status.connected::before { background: var(--green); box-shadow: 0 0 6px var(--green); } @@ -135,35 +163,42 @@ html, body { animation: status-pulse 2.5s ease-in-out infinite; } -.theme-toggle { + + +.terminal-container.scroll-x { overflow-x: auto; } +.terminal-container.scroll-x .terminal-output { + white-space: pre; + word-break: normal; +} + +.reload-btn { background: none; border: none; color: var(--text-secondary); - font-size: 1rem; + font-size: 1.15rem; padding: 0; cursor: pointer; - width: 32px; - height: 32px; + width: 36px; + height: 36px; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-sm); - transition: background var(--transition-fast), color var(--transition-fast); + transition: background var(--transition-fast), color var(--transition-fast), transform 0.3s ease; flex-shrink: 0; } +.reload-btn:hover { background: var(--bg-tertiary); color: var(--text-primary); } +.reload-btn:active { transform: rotate(180deg); } -.theme-toggle:hover { - background: var(--bg-tertiary); - color: var(--text-primary); -} /* ── Terminal container ──────────────────────────── */ .terminal-container { flex: 1; overflow-y: auto; - overflow-x: auto; + overflow-x: hidden; -webkit-overflow-scrolling: touch; background: var(--terminal-bg); + position: relative; } .terminal-container::-webkit-scrollbar { @@ -177,16 +212,59 @@ html, body { border-radius: 2px; } -/* ── QR button ───────────────────────────────────── */ -.qr-btn { +/* ── Desktop layout ──────────────────────────────── */ +@media (min-width: 1024px) { + body { + display: flex; + flex-direction: row; + } + + .sidebar-overlay { + display: none; + } + + .app-container { + flex: 1; + min-width: 0; + } + + .top-bar { + height: 50px; + padding: 0 18px; + } + +} + +@media (prefers-reduced-motion: reduce) { + .connection-status:not(.connected) { animation: none; } +} + +.connection-status.reconnecting { + background: var(--yellow); + box-shadow: 0 0 5px var(--yellow); +} + +.terminal-container.stale { position: relative; } +.terminal-container.stale::after { + content: ''; + position: absolute; + inset: 0; + background: var(--bg-primary); + opacity: 0.4; + pointer-events: none; + z-index: 1; +} + +/* ── Settings button ─────────────────────────── */ +.settings-btn { background: none; border: none; color: var(--text-secondary); - font-size: 1.1rem; + font-size: 1.05rem; padding: 0; cursor: pointer; - width: 32px; - height: 32px; + width: 36px; + height: 36px; display: flex; align-items: center; justify-content: center; @@ -194,129 +272,124 @@ html, body { transition: background var(--transition-fast), color var(--transition-fast); flex-shrink: 0; } +.settings-btn:hover { background: var(--bg-tertiary); color: var(--text-primary); } -.qr-btn:hover { - background: var(--bg-tertiary); - color: var(--text-primary); -} - -/* ── QR modal ────────────────────────────────────── */ -.qr-modal-overlay { +/* ── Settings panel ──────────────────────────── */ +.settings-overlay { + display: none; position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.65); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); + background: rgba(0, 0, 0, 0.4); z-index: 200; - display: flex; - align-items: center; - justify-content: center; + -webkit-backdrop-filter: blur(2px); + backdrop-filter: blur(2px); } +.settings-overlay.visible { display: block; } -.qr-modal-overlay[hidden] { - display: none; -} - -.qr-modal { - position: relative; +.settings-panel { + position: fixed; + top: 0; + right: -280px; + width: 260px; + height: 100%; + padding-top: env(safe-area-inset-top); background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg, 12px); - padding: 24px; + border-left: 1px solid var(--border-color); + z-index: 201; display: flex; flex-direction: column; - align-items: center; - gap: 16px; - max-width: min(360px, 90vw); - width: 100%; - box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5); + transition: right var(--transition-normal); } +.settings-panel.open { right: 0; } -.qr-modal-close { - position: absolute; - top: 10px; - right: 10px; +.settings-header { + display: flex; + align-items: center; + justify-content: space-between; + height: 48px; + padding: 0 16px; + border-bottom: 1px solid var(--border-color); + font-weight: 600; + font-size: 0.88rem; + color: var(--text-primary); + flex-shrink: 0; +} +.settings-close { background: none; border: none; color: var(--text-secondary); - font-size: 1.4rem; - line-height: 1; + font-size: 1.2rem; cursor: pointer; - padding: 4px 8px; + padding: 0; + width: 28px; + height: 28px; border-radius: var(--radius-sm); - transition: background var(--transition-fast), color var(--transition-fast); + display: flex; + align-items: center; + justify-content: center; } +.settings-close:hover { background: var(--bg-tertiary); color: var(--text-primary); } -.qr-modal-close:hover { - background: var(--bg-tertiary); - color: var(--text-primary); -} +.settings-body { flex: 1; overflow-y: auto; padding: 8px 0; } -.qr-modal-image { - width: min(260px, 80vw); - height: min(260px, 80vw); - display: block; - border-radius: 8px; - background: #fff; - padding: 8px; +.settings-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 13px 16px; + cursor: pointer; + transition: background var(--transition-fast); + -webkit-user-select: none; + user-select: none; } +.settings-row:hover { background: var(--bg-tertiary); } +.settings-row:active { background: var(--bg-tertiary); } -.qr-modal-url { - font-size: 0.72rem; - color: var(--text-secondary); - word-break: break-all; - text-align: center; - max-width: 100%; - font-family: 'SF Mono', 'Menlo', 'Monaco', 'Consolas', monospace; +.settings-label { + font-size: 0.88rem; + color: var(--text-primary); } -/* ── Desktop layout ──────────────────────────────── */ -@media (min-width: 1024px) { - body { - display: flex; - flex-direction: row; - } - - .sidebar-overlay { - display: none; - } - - .app-container { - flex: 1; - min-width: 0; - } - - .top-bar { - height: 50px; - padding: 0 18px; - } - - .menu-btn { - display: none; - } +/* Toggle switch */ +.settings-toggle { + position: relative; + width: 44px; + height: 26px; + flex-shrink: 0; } -@media (prefers-reduced-motion: reduce) { - .connection-status:not(.connected) { animation: none; } +.settings-cb { + position: absolute; + opacity: 0; + width: 0; + height: 0; } -.connection-status.reconnecting { - background: rgba(251, 191, 36, 0.08); - border-color: rgba(251, 191, 36, 0.15); - color: var(--yellow); -} -.connection-status.reconnecting::before { - background: var(--yellow); - box-shadow: 0 0 5px var(--yellow); +.settings-toggle-slider { + position: absolute; + inset: 0; + background: var(--border-color); + border-radius: 26px; + transition: background 0.2s; } -.terminal-container.stale { position: relative; } -.terminal-container.stale::after { +.settings-toggle-slider::before { content: ''; position: absolute; - inset: 0; - background: var(--bg-primary); - opacity: 0.4; - pointer-events: none; - z-index: 1; + width: 20px; + height: 20px; + left: 3px; + top: 3px; + background: #fff; + border-radius: 50%; + transition: transform 0.2s; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +.settings-cb:checked + .settings-toggle-slider { + background: var(--accent); +} + +.settings-cb:checked + .settings-toggle-slider::before { + transform: translateX(18px); } diff --git a/public/css/sidebar.css b/public/css/sidebar.css index 827e2a3..3180c93 100644 --- a/public/css/sidebar.css +++ b/public/css/sidebar.css @@ -82,7 +82,8 @@ .sidebar-content { flex: 1; overflow-y: auto; - padding: 10px 0 16px; + overflow-x: hidden; + padding: 10px 0 20px; } .sidebar-content::-webkit-scrollbar { @@ -95,24 +96,30 @@ /* ── Group label ─────────────────────────────────── */ .ws-group-label { - padding: 14px 16px 5px; - font-size: 0.65rem; + padding: 16px 16px 7px; + font-size: 0.78rem; text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--text-dimmed); + letter-spacing: 0.08em; + color: var(--text-secondary); font-weight: 700; } +.ws-group-label:not(:first-child) { + margin-top: 6px; + border-top: 1px solid var(--border-color); + padding-top: 18px; +} + /* ── Workspace item ──────────────────────────────── */ .ws-item { - padding: 9px 14px; + padding: 10px 14px; cursor: pointer; display: flex; - align-items: center; - gap: 9px; - font-size: 0.86rem; + align-items: flex-start; + gap: 10px; + font-size: 0.95rem; font-weight: 500; - color: var(--text-secondary); + color: var(--text-primary); transition: background var(--transition-fast), color var(--transition-fast); margin: 1px 8px; border-radius: var(--radius-sm); @@ -130,30 +137,32 @@ .ws-item .ws-icon { color: var(--text-dimmed); - font-size: 0.8rem; + font-size: 0.85rem; width: 18px; text-align: center; flex-shrink: 0; + padding-top: 2px; } -.ws-item .ws-label { +.ws-item .ws-label, +.surface-item .ws-label { flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + word-break: break-word; + overflow-wrap: break-word; + line-height: 1.45; } /* ── Surface item ────────────────────────────────── */ .surface-item { - padding: 8px 12px; + padding: 10px 14px; cursor: pointer; - font-size: 0.83rem; - color: var(--text-secondary); + font-size: 0.95rem; + color: var(--text-primary); display: flex; - align-items: center; - gap: 8px; + align-items: flex-start; + gap: 10px; transition: background var(--transition-fast), color var(--transition-fast); - margin: 1px 8px; + margin: 2px 8px; border-radius: var(--radius-sm); position: relative; } @@ -182,41 +191,73 @@ border-radius: 0 var(--radius-pill) var(--radius-pill) 0; } -.surface-item .surface-icon { - font-size: 0.75rem; - width: 16px; - text-align: center; +.surface-status { + width: 8px; + height: 8px; + border-radius: 50%; flex-shrink: 0; - opacity: 0.6; + margin-top: 6px; + background: var(--text-dimmed); + transition: background 0.3s ease; +} + +.surface-status.idle { + background: var(--yellow); + opacity: 0.7; } -.surface-item.active .surface-icon { +.surface-status.working { + background: var(--green); + animation: status-pulse 1.8s ease-in-out infinite; +} + +.surface-item.active .surface-status { opacity: 1; } +@keyframes status-pulse { + 0%, 100% { + opacity: 1; + box-shadow: 0 0 4px var(--green-glow); + } + 50% { + opacity: 0.3; + box-shadow: 0 0 8px var(--green-glow); + } +} + /* ── Desktop ─────────────────────────────────────── */ @media (min-width: 1024px) { .sidebar { position: relative; transform: none; - width: 252px; + width: 290px; flex-shrink: 0; box-shadow: none; height: 100vh; height: 100dvh; + overflow: hidden; + transition: width 0.22s ease, border-color 0.22s ease; } .sidebar.open { transform: none; } - .sidebar-overlay { - display: none; + .sidebar.desktop-hidden { + width: 0; + border-right-color: transparent; } - .sidebar-close { + .sidebar-overlay { display: none; } + + /* Font sizes smaller on desktop — mobile sizes are larger for touch */ + .ws-group-label { font-size: 0.7rem; padding-top: 14px; } + .ws-group-label:not(:first-child) { padding-top: 16px; } + .ws-item { font-size: 0.86rem; padding: 8px 12px; } + .surface-item { font-size: 0.84rem; padding: 8px 12px; margin: 1px 8px; } } .sidebar-empty { diff --git a/public/css/terminal.css b/public/css/terminal.css index c920fc3..fffaaaf 100644 --- a/public/css/terminal.css +++ b/public/css/terminal.css @@ -7,16 +7,143 @@ unicode-range: U+23FB-23FE, U+2665, U+26A1, U+2B58, U+E000-E00A, U+E0A0-E0A2, U+E0A3, U+E0B0-E0C8, U+E0CA, U+E0CC-E0D7, U+E200-E2A9, U+E300-E3E3, U+E5FA-E6B7, U+E700-E7C5, U+EA60-EBEB, U+F000-F2FF, U+F400-F533, U+F0001-F1AF0; } +.terminal-container.browser-mode { + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +.browser-view[hidden], +.browser-zoom-btn[hidden] { display: none; } + +.browser-view { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; + display: block; + background: #000; +} + +.browser-loading[hidden] { display: none; } +.browser-loading { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + z-index: 30; + overflow: hidden; +} +.browser-loading::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(90deg, transparent 0%, var(--accent) 50%, transparent 100%); + background-size: 40% 100%; + background-repeat: no-repeat; + background-position: -40% 0; + animation: browser-sweep 1.6s ease-in-out infinite; + opacity: 0; + transition: opacity 0.3s; +} +.browser-loading.flash::after { + opacity: 1; +} +@keyframes browser-sweep { + 0% { background-position: -40% 0; } + 100% { background-position: 140% 0; } +} + +.browser-zoom-btn { + position: absolute; + top: 10px; + right: 10px; + z-index: 20; + width: 36px; + height: 36px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.55); + border: 1px solid rgba(255, 255, 255, 0.15); + color: #fff; + font-size: 1rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + transition: background 0.15s; +} +.browser-zoom-btn:active { background: rgba(255, 255, 255, 0.2); } + +/* Lightbox */ +.img-zoom-overlay[hidden] { display: none; } +.img-zoom-overlay { + position: fixed; + inset: 0; + background: #000; + z-index: 500; + display: flex; + align-items: stretch; + animation: fadeIn 0.18s ease; +} +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } + +.img-zoom-close { + position: absolute; + top: max(16px, env(safe-area-inset-top)); + right: 16px; + z-index: 10; + width: 36px; + height: 36px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.15); + border: none; + color: #fff; + font-size: 1rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} +.img-zoom-close:active { background: rgba(255, 255, 255, 0.3); } + +.img-zoom-container { + flex: 1; + overflow: hidden; + cursor: grab; + display: flex; + align-items: center; + justify-content: center; + touch-action: none; +} +.img-zoom-container.dragging { cursor: grabbing; } + +.img-zoom-img { + max-width: 100%; + max-height: 100%; + pointer-events: none; + user-select: none; + -webkit-user-drag: none; + transform-origin: center center; + will-change: transform; +} + .terminal-output { margin: 0; - padding: 12px 16px; + padding: 14px 16px; font-family: 'SF Mono', 'JetBrains Mono', 'Cascadia Code', 'Fira Code', 'Menlo', 'Monaco', 'Consolas', 'SymbolsNerdFont', 'Symbols Nerd Font Mono', monospace; - font-size: 13px; - line-height: 1.0; - white-space: pre; - word-wrap: normal; + font-size: 15px; + line-height: 1.2; + white-space: pre-wrap; + word-break: break-all; color: var(--text-primary); - contain: content; + contain: style; min-height: 100%; -webkit-user-select: text; user-select: text; @@ -36,7 +163,7 @@ } .hl-thinking { - color: var(--purple); + color: var(--yellow); } .hl-output { @@ -86,8 +213,8 @@ } -/* QR code lines — font-size set dynamically in terminal-view.js to fit width */ -.qr-line { +/* QR block — font-size set dynamically in terminal-view.js to fit width */ +.qr-block { display: block; line-height: 1.0; letter-spacing: -0.03em; diff --git a/public/index.html b/public/index.html index 50ad2e5..cfbd4c5 100644 --- a/public/index.html +++ b/public/index.html @@ -6,13 +6,15 @@