From 813775cb501662d58e49cc693bda04935551ce07 Mon Sep 17 00:00:00 2001 From: N1C4T Date: Sun, 22 Feb 2026 04:52:21 +0400 Subject: [PATCH] feat: added sidebar, removed verbose comments, fixed npm audits --- .gitignore | 2 +- package-lock.json | 384 ++++++++-------- package.json | 7 +- src/app/globals.css | 125 ++++-- src/app/page.tsx | 161 ++++--- src/components/app/AppIcon.tsx | 8 +- src/components/app/AppItem.tsx | 37 +- src/components/app/CategoryHeader.tsx | 22 +- src/components/app/CategorySection.tsx | 28 +- src/components/command/AurDrawerSettings.tsx | 15 +- src/components/command/AurFloatingCard.tsx | 66 +-- src/components/command/AurPopover.tsx | 6 +- src/components/command/CommandDrawer.tsx | 53 +-- src/components/command/CommandFooter.tsx | 116 ++--- src/components/command/ShortcutsBar.tsx | 15 +- src/components/common/Tooltip.tsx | 46 +- src/components/distro/DistroSelector.tsx | 10 +- src/components/header/HowItWorks.tsx | 36 +- src/components/sidebar/Sidebar.tsx | 450 +++++++++++++++++++ src/components/sidebar/index.ts | 3 + src/components/ui/theme-toggle.tsx | 14 +- src/hooks/useKeyboardNavigation.ts | 23 +- src/hooks/useLinuxInit.ts | 29 -- src/hooks/useTheme.tsx | 8 +- src/hooks/useTooltip.ts | 18 +- src/hooks/useVerification.ts | 4 - src/lib/verified-flatpaks.json | 400 ++++++++++++++++- 27 files changed, 1423 insertions(+), 663 deletions(-) create mode 100644 src/components/sidebar/Sidebar.tsx create mode 100644 src/components/sidebar/index.ts diff --git a/.gitignore b/.gitignore index b04e5ab..9a41fda 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,4 @@ npm-debug.log* next-env.d.ts # local notes -AGENT.md +AGENT.md \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8be6e55..634f216 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "framer-motion": "^12.23.26", "gsap": "^3.14.2", "lucide-react": "^0.561.0", - "next": "16.0.10", + "next": "^16.1.6", "react": "19.2.1", "react-dom": "19.2.1", "tailwind-merge": "^3.4.0" @@ -26,7 +26,7 @@ "@types/react-dom": "^19", "@vitejs/plugin-react": "^5.1.2", "eslint": "^9", - "eslint-config-next": "16.0.10", + "eslint-config-next": "^16.1.6", "jsdom": "^27.4.0", "tailwindcss": "^4", "typescript": "^5", @@ -1018,9 +1018,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1125,9 +1125,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", "dev": true, "license": "MIT", "engines": { @@ -1761,15 +1761,15 @@ } }, "node_modules/@next/env": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz", - "integrity": "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.10.tgz", - "integrity": "sha512-b2NlWN70bbPLmfyoLvvidPKWENBYYIe017ZGUpElvQjDytCWgxPJx7L9juxHt0xHvNVA08ZHJdOyhGzon/KJuw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz", + "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1777,9 +1777,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.10.tgz", - "integrity": "sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", "cpu": [ "arm64" ], @@ -1793,9 +1793,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.10.tgz", - "integrity": "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", "cpu": [ "x64" ], @@ -1809,9 +1809,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.10.tgz", - "integrity": "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", "cpu": [ "arm64" ], @@ -1825,9 +1825,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.10.tgz", - "integrity": "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", "cpu": [ "arm64" ], @@ -1841,9 +1841,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.10.tgz", - "integrity": "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", "cpu": [ "x64" ], @@ -1857,9 +1857,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.10.tgz", - "integrity": "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", "cpu": [ "x64" ], @@ -1873,9 +1873,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.10.tgz", - "integrity": "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", "cpu": [ "arm64" ], @@ -1889,9 +1889,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.10.tgz", - "integrity": "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", "cpu": [ "x64" ], @@ -2782,20 +2782,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", - "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz", + "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/type-utils": "8.50.0", - "@typescript-eslint/utils": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/type-utils": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2805,8 +2805,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.50.0", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -2821,17 +2821,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz", - "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz", + "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2841,20 +2841,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", - "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz", + "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.0", - "@typescript-eslint/types": "^8.50.0", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.56.0", + "@typescript-eslint/types": "^8.56.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2868,14 +2868,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", - "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz", + "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0" + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2886,9 +2886,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", - "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz", + "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==", "dev": true, "license": "MIT", "engines": { @@ -2903,17 +2903,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", - "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz", + "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2923,14 +2923,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", - "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz", + "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==", "dev": true, "license": "MIT", "engines": { @@ -2942,21 +2942,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", - "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz", + "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.50.0", - "@typescript-eslint/tsconfig-utils": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/visitor-keys": "8.50.0", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", + "@typescript-eslint/project-service": "8.56.0", + "@typescript-eslint/tsconfig-utils": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2969,36 +2969,10 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3009,16 +2983,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz", - "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz", + "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.50.0", - "@typescript-eslint/types": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3028,19 +3002,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", - "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz", + "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.56.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3050,6 +3024,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", @@ -3485,9 +3472,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3769,17 +3756,19 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "20 || >=22" + } }, "node_modules/baseline-browser-mapping": { "version": "2.9.8", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.8.tgz", "integrity": "sha512-Y1fOuNDowLfgKOypdc9SPABfoWXuZHBOyCS4cD52IeZBhr4Md6CLLs6atcxVrzRmQ06E7hSlm5bHHApPKR/byA==", - "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -3796,14 +3785,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/braces": { @@ -3995,13 +3986,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4558,9 +4542,9 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", "dependencies": { @@ -4570,7 +4554,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", + "@eslint/js": "9.39.3", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4618,13 +4602,13 @@ } }, "node_modules/eslint-config-next": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.0.10.tgz", - "integrity": "sha512-BxouZUm0I45K4yjOOIzj24nTi0H2cGo0y7xUmk+Po/PYtJXFBYVDS1BguE7t28efXjKdcN0tmiLivxQy//SsZg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz", + "integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "16.0.10", + "@next/eslint-plugin-next": "16.1.6", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", @@ -5053,9 +5037,9 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -6618,16 +6602,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -6704,13 +6691,14 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/next/-/next-16.0.10.tgz", - "integrity": "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", "dependencies": { - "@next/env": "16.0.10", + "@next/env": "16.1.6", "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -6722,14 +6710,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.0.10", - "@next/swc-darwin-x64": "16.0.10", - "@next/swc-linux-arm64-gnu": "16.0.10", - "@next/swc-linux-arm64-musl": "16.0.10", - "@next/swc-linux-x64-gnu": "16.0.10", - "@next/swc-linux-x64-musl": "16.0.10", - "@next/swc-win32-arm64-msvc": "16.0.10", - "@next/swc-win32-x64-msvc": "16.0.10", + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { @@ -8137,9 +8125,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -8287,16 +8275,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz", - "integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz", + "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.50.0", - "@typescript-eslint/parser": "8.50.0", - "@typescript-eslint/typescript-estree": "8.50.0", - "@typescript-eslint/utils": "8.50.0" + "@typescript-eslint/eslint-plugin": "8.56.0", + "@typescript-eslint/parser": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8306,7 +8294,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, diff --git a/package.json b/package.json index 252eb75..83e4f53 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "framer-motion": "^12.23.26", "gsap": "^3.14.2", "lucide-react": "^0.561.0", - "next": "16.0.10", + "next": "^16.1.6", "react": "19.2.1", "react-dom": "19.2.1", "tailwind-merge": "^3.4.0" @@ -31,10 +31,13 @@ "@types/react-dom": "^19", "@vitejs/plugin-react": "^5.1.2", "eslint": "^9", - "eslint-config-next": "16.0.10", + "eslint-config-next": "^16.1.6", "jsdom": "^27.4.0", "tailwindcss": "^4", "typescript": "^5", "vitest": "^4.0.16" + }, + "overrides": { + "minimatch": "^10.2.1" } } \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 8dc10d4..05f5c86 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,9 +1,9 @@ @import "tailwindcss"; -/* ===== WARM PAPER AESTHETIC THEMES ===== */ + :root { - /* Dark theme - lighter warm charcoal per user request */ + zoom: 1.07; --bg-primary: #262522; --bg-secondary: #2f2e2a; --bg-tertiary: #3a3934; @@ -18,7 +18,6 @@ } .light { - /* Light theme - warm paper/parchment */ --bg-primary: #f5f2ed; --bg-secondary: #ebe8e2; --bg-tertiary: #e0dcd4; @@ -71,7 +70,6 @@ body::before { mix-blend-mode: overlay; } -/* Smooth scrollbar */ ::-webkit-scrollbar { width: 6px; height: 6px; @@ -86,10 +84,8 @@ body::before { border-radius: 3px; } -/* Scroll margin to keep focused items visible during keyboard navigation */ [data-nav-id] { scroll-margin-block: 100px 150px; - /* top: 100px, bottom: 150px for shortcuts bar */ } html { @@ -101,8 +97,6 @@ html { background: rgba(128, 120, 100, 0.25); } -/* ===== THEME SWITCH ===== */ - .switch { position: relative; -webkit-tap-highlight-color: transparent; @@ -249,7 +243,6 @@ html { height: 1px; } -/* Checked state (dark mode) */ .switch__input:checked { background-color: var(--bg-tertiary); } @@ -312,14 +305,10 @@ html { transform: translateX(-50%) rotate(315deg) translateY(0.625em) scale(0); } -/* Set switch size */ .switch { font-size: 1.5rem; } -/* ===== ANIMATIONS ===== */ - -/* Checkbox pop */ @keyframes checkPop { 0% { transform: scale(0.8); @@ -338,7 +327,6 @@ html { animation: checkPop 0.2s cubic-bezier(0.34, 1.56, 0.64, 1); } -/* Checkmark entrance */ @keyframes checkIn { 0% { transform: scale(0) rotate(-45deg); @@ -355,12 +343,10 @@ html { animation: checkIn 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; } -/* Spring chevron */ .chevron-spring { transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); } -/* Stagger items */ @keyframes staggerIn { 0% { opacity: 0; @@ -377,7 +363,6 @@ html { animation: staggerIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards; } -/* Tooltip fade */ @keyframes tooltipIn { 0% { opacity: 0; @@ -394,7 +379,6 @@ html { animation: tooltipIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards; } -/* Dropdown entrance */ @keyframes dropIn { 0% { opacity: 0; @@ -411,7 +395,6 @@ html { animation: dropIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; } -/* Button press */ .btn-press { transition: transform 0.1s ease; } @@ -420,15 +403,11 @@ html { transform: scale(0.95); } -/* Focus row highlight */ .focus-row { background: var(--bg-focus); transition: background 0.15s ease; } -/* ===== ENTRANCE ANIMATION INITIAL STATES ===== */ -/* These prevent the flash of content before GSAP animates */ - .category-header { clip-path: inset(0 100% 0 0); } @@ -448,8 +427,6 @@ html { transform: translateY(-10px); } -/* ===== HOW IT WORKS POPUP ===== */ - @keyframes popupSlideIn { 0% { opacity: 0; @@ -482,8 +459,6 @@ html { border-radius: 2px; } -/* ===== SLIDE-UP DRAWER ANIMATIONS ===== */ - @keyframes fadeIn { 0% { opacity: 0; @@ -574,7 +549,6 @@ html { } } -/* Smooth spring-like animations for floating cards */ @keyframes cardSlideIn { 0% { opacity: 0; @@ -619,7 +593,6 @@ html { } } -/* Slide in from right edge */ @keyframes slideInFromRight { 0% { opacity: 0; @@ -676,18 +649,14 @@ html { } } -/* ===== COMMAND BAR SCROLLBAR ===== */ - .command-scroll { scrollbar-width: none; - /* Firefox */ -ms-overflow-style: none; /* IE/Edge */ } .command-scroll::-webkit-scrollbar { display: none; - /* Chrome/Safari/Opera */ } /* ===== SEARCH POPUP ANIMATION ===== */ @@ -704,8 +673,6 @@ html { } } -/* ===== THEME FLASH ANIMATION ===== */ - @keyframes themeFlash { 0% { opacity: 0; @@ -730,8 +697,6 @@ body.theme-flash::after { animation: themeFlash 0.15s ease-out forwards; } -/* ===== SKELETON LOADING ANIMATION ===== */ - @keyframes skeletonPulse { 0%, @@ -749,13 +714,11 @@ body.theme-flash::after { will-change: opacity; } -/* Staggered skeleton items for wave effect */ .skeleton-item { animation: skeletonPulse 1.5s ease-in-out infinite; will-change: opacity; } -/* Force GPU acceleration for animated elements */ .category-header, .app-item, .header-animate, @@ -764,4 +727,88 @@ body.theme-flash::after { backface-visibility: hidden; -webkit-perspective: 1000px; perspective: 1000px; +} + +@keyframes sidebarSlideIn { + 0% { + opacity: 0; + transform: translateX(-30px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.sidebar { + animation: sidebarSlideIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; + background: var(--bg-primary); + border-right: 1px solid color-mix(in srgb, var(--border-primary), transparent 50%); +} + +.sidebar-scroll::-webkit-scrollbar { + width: 3px; +} + +.sidebar-scroll::-webkit-scrollbar-track { + background: transparent; +} + +.sidebar-scroll::-webkit-scrollbar-thumb { + background: color-mix(in srgb, var(--text-muted), transparent 70%); + border-radius: 3px; +} + +.sidebar-scroll::-webkit-scrollbar-thumb:hover { + background: color-mix(in srgb, var(--text-muted), transparent 40%); +} + +.sidebar-action-btn { + transition: background-color 0.15s ease, color 0.15s ease, opacity 0.15s ease; +} + +.sidebar-action-btn:hover:not(:disabled) { + background-color: var(--bg-hover); +} + +.sidebar-action-btn:active:not(:disabled) { + opacity: 0.8; +} + +.sidebar-search { + transition: border-color 0.2s ease, background-color 0.2s ease; +} + +.sidebar-search:focus-within { + border-color: var(--accent); + background: var(--bg-secondary); +} + +.sidebar-command-preview { + transition: background-color 0.15s ease; +} + +.sidebar-command-preview:hover { + background: var(--bg-hover); +} + +.sidebar-distro-btn { + transition: background-color 0.15s ease; +} + +.sidebar-distro-btn:hover { + filter: brightness(1.08); +} + +@media (min-width: 1024px) { + .main-with-sidebar { + margin-left: 380px; + } +} + +@media (min-width: 1280px) { + .main-with-sidebar { + margin-left: 420px; + } } \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index feef525..0515f79 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,22 +3,19 @@ import { useState, useMemo, useCallback, useRef, useLayoutEffect, useEffect } from 'react'; import gsap from 'gsap'; -// Hooks import { useLinuxInit } from '@/hooks/useLinuxInit'; import { useTooltip } from '@/hooks/useTooltip'; import { useKeyboardNavigation, type NavItem } from '@/hooks/useKeyboardNavigation'; import { useVerification } from '@/hooks/useVerification'; - -// Data import { categories, getAppsByCategory } from '@/lib/data'; -// Components import { ThemeToggle } from '@/components/ui/theme-toggle'; import { HowItWorks, GitHubLink, ContributeLink } from '@/components/header'; import { DistroSelector } from '@/components/distro'; import { CategorySection } from '@/components/app'; import { CommandFooter } from '@/components/command'; import { Tooltip, GlobalStyles, LoadingSkeleton } from '@/components/common'; +import { Sidebar } from '@/components/sidebar'; export default function Home() { @@ -44,40 +41,104 @@ export default function Home() { unfreeAppNames, } = useLinuxInit(); - // Search state const [searchQuery, setSearchQuery] = useState(''); const searchInputRef = useRef(null); - // Verification status for Flatpak/Snap apps + const [drawerOpen, setDrawerOpen] = useState(false); + const [drawerClosing, setDrawerClosing] = useState(false); + + const closeDrawer = useCallback(() => { + setDrawerClosing(true); + setTimeout(() => { + setDrawerOpen(false); + setDrawerClosing(false); + }, 250); + }, []); + + const openDrawer = useCallback(() => { + if (selectedCount > 0) setDrawerOpen(true); + }, [selectedCount]); + + const [activeShortcut, setActiveShortcut] = useState(null); + + const toggleThemeWithFlash = useCallback(() => { + document.body.classList.add('theme-flash'); + setTimeout(() => document.body.classList.remove('theme-flash'), 150); + const themeBtn = document.querySelector('[aria-label="Toggle theme"]') as HTMLButtonElement; + if (themeBtn) themeBtn.click(); + }, []); + const { isVerified, getVerificationSource } = useVerification(); - // Handle "/" key to focus search useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - // Skip if already in input - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLSelectElement) return; - // Skip if modifier keys are pressed (prevents conflicts with browser shortcuts) if (e.ctrlKey || e.altKey || e.metaKey) return; if (e.key === '/') { e.preventDefault(); - searchInputRef.current?.focus(); + const inputs = document.querySelectorAll('input[placeholder="Search apps..."]'); + const visibleInput = Array.from(inputs).find(input => input.offsetParent !== null); + if (visibleInput) visibleInput.focus(); + return; + } + + const alwaysEnabled = ['t', 'c', '?']; + if (selectedCount === 0 && !alwaysEnabled.includes(e.key)) return; + + switch (e.key) { + case 'y': + setActiveShortcut('y'); + setTimeout(() => setActiveShortcut(null), 150); + const copyBtns = document.querySelectorAll('[data-action="copy"]'); + const visibleCopyBtn = Array.from(copyBtns).find(b => b.offsetParent !== null); + if (visibleCopyBtn) visibleCopyBtn.click(); + break; + case 'd': + setActiveShortcut('d'); + setTimeout(() => setActiveShortcut(null), 150); + const dlBtns = document.querySelectorAll('[data-action="download"]'); + const visibleDlBtn = Array.from(dlBtns).find(b => b.offsetParent !== null); + if (visibleDlBtn) visibleDlBtn.click(); + break; + case 't': + setActiveShortcut('t'); + setTimeout(() => setActiveShortcut(null), 150); + toggleThemeWithFlash(); + break; + case 'c': + setActiveShortcut('c'); + setTimeout(() => setActiveShortcut(null), 150); + clearAll(); + break; + case '1': + if (hasAurPackages) setSelectedHelper('yay'); + break; + case '2': + if (hasAurPackages) setSelectedHelper('paru'); + break; + case 'Tab': + e.preventDefault(); + if (selectedCount > 0) { + if (drawerOpen) { + closeDrawer(); + } else { + openDrawer(); + } + } + break; } }; - window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, []); + }, [selectedCount, clearAll, hasAurPackages, setSelectedHelper, drawerOpen, closeDrawer, openDrawer, toggleThemeWithFlash]); - - // Distribute apps into a nice grid const allCategoriesWithApps = useMemo(() => { const query = searchQuery.toLowerCase().trim(); return categories .map(cat => { const categoryApps = getAppsByCategory(cat); - // Filter apps if there's a search query (match name or id only) const filteredApps = query ? categoryApps.filter(app => app.name.toLowerCase().includes(query) || @@ -89,10 +150,8 @@ export default function Home() { .filter(c => c.apps.length > 0); }, [searchQuery]); - // 5 columns looks good on most screens - const COLUMN_COUNT = 5; + const COLUMN_COUNT = 4; - // Pack categories into shortest column while preserving order const columns = useMemo(() => { const cols: Array = Array.from({ length: COLUMN_COUNT }, () => []); const heights = Array(COLUMN_COUNT).fill(0); @@ -106,9 +165,6 @@ export default function Home() { return cols; }, [allCategoriesWithApps]); - // Category expansion - all open by default because hiding stuff is annoying - // ======================================================================== - const [expandedCategories, setExpandedCategories] = useState>(() => new Set(categories)); const toggleCategoryExpanded = useCallback((cat: string) => { @@ -123,8 +179,6 @@ export default function Home() { }); }, []); - - // Build nav items for keyboard navigation const navItems = useMemo(() => { const items: NavItem[][] = []; columns.forEach((colCategories) => { @@ -146,9 +200,6 @@ export default function Home() { toggleApp ); - // Header animation - makes the logo look fancy on first load - // ======================================================================== - const headerRef = useRef(null); useLayoutEffect(() => { @@ -158,7 +209,6 @@ export default function Home() { const title = header.querySelector('.header-animate'); const controls = header.querySelector('.header-controls'); - // Fancy clip-path reveal for the logo gsap.fromTo(title, { clipPath: 'inset(0 100% 0 0)' }, { @@ -172,7 +222,6 @@ export default function Home() { } ); - // Animate controls with fade-in gsap.fromTo(controls, { opacity: 0, y: -10 }, { @@ -185,17 +234,10 @@ export default function Home() { ); }, [isHydrated]); - - // Don't render until we've loaded from localStorage (avoids flash) - - // Show loading skeleton until localStorage is hydrated if (!isHydrated) { return ; } - // Finally, the actual page - // ======================================================================== - return (
- {/* Header */} -
+ + +
- {/* Logo & Title */}
- {/* eslint-disable-next-line @next/next/no-img-element */} TuxMate Logo
- {/* Header Controls */}
{/* Left side on mobile: Help + Links */}
- {/* Help - mobile only here, desktop is in title area */}
@@ -247,7 +303,6 @@ export default function Home() {
- {/* Right side: Theme + Distro (with separator on desktop) */}
@@ -257,13 +312,10 @@ export default function Home() {
- {/* App Grid */} -
-
- {/* Mobile: 2-column grid with balanced distribution */} -
+
+
+
{(() => { - // Pack into 2 columns on mobile const mobileColumns: Array = [[], []]; const heights = [0, 0]; allCategoriesWithApps.forEach(catData => { @@ -300,16 +352,13 @@ export default function Home() { })()}
- {/* Desktop: Grid with Tetris packing */} -
+
{columns.map((columnCategories, colIdx) => { - // Calculate starting index for this column (for staggered animation) let globalIdx = 0; for (let c = 0; c < colIdx; c++) { globalIdx += columns[c].length; } - // Generate stable key based on column content to ensure proper reconciliation const columnKey = `col-${colIdx}-${columnCategories.map(c => c.category).join('-')}`; return ( @@ -343,7 +392,6 @@ export default function Home() {
- {/* Command Footer */} setDrawerOpen(true)} + onDrawerClose={closeDrawer} + activeShortcut={activeShortcut} />
); diff --git a/src/components/app/AppIcon.tsx b/src/components/app/AppIcon.tsx index fa3ad3d..34a78f6 100644 --- a/src/components/app/AppIcon.tsx +++ b/src/components/app/AppIcon.tsx @@ -8,7 +8,7 @@ export function AppIcon({ url, name }: { url: string; name: string }) { if (error) { return ( -
+
{name[0]}
); @@ -20,9 +20,9 @@ export function AppIcon({ url, name }: { url: string; name: string }) { src={url} alt="" aria-hidden="true" - width={16} - height={16} - className="w-[18px] h-[18px] object-contain opacity-75" + width={22} + height={22} + className="w-[22px] h-[22px] object-contain opacity-75" onError={() => setError(true)} loading="lazy" /> diff --git a/src/components/app/AppItem.tsx b/src/components/app/AppItem.tsx index 071464e..09c4998 100644 --- a/src/components/app/AppItem.tsx +++ b/src/components/app/AppItem.tsx @@ -5,15 +5,7 @@ import { Check } from 'lucide-react'; import { distros, type DistroId, type AppData } from '@/lib/data'; import { isAurPackage } from '@/lib/aur'; import { AppIcon } from './AppIcon'; -// import { analytics } from '@/lib/analytics'; // Uncomment to enable app selection tracking - -/** - * Individual app row in the category list. - * Memoized because we render hundreds of these and React was having a moment. - * Handles selection state, availability indicators, AUR badges, and tooltips. - */ - -// Tailwind colors as hex - because CSS variables don't work in inline styles +// Individual app item. const COLOR_MAP: Record = { 'orange': '#f97316', 'blue': '#3b82f6', @@ -43,7 +35,6 @@ interface AppItemProps { onTooltipLeave: () => void; onFocus?: () => void; color?: string; - // Flatpak/Snap verification status isVerified?: boolean; verificationSource?: 'flathub' | 'snap' | null; } @@ -62,16 +53,12 @@ export const AppItem = memo(function AppItem({ isVerified = false, verificationSource = null, }: AppItemProps) { - // Why isn't this app available? Tell the user. const getUnavailableText = () => { const distroName = distros.find(d => d.id === selectedDistro)?.name || ''; return app.unavailableReason || `Not available in ${distroName} repos`; }; - // Special styling for AUR packages (Arch users love their badges) const isAur = selectedDistro === 'arch' && app.targets?.arch && isAurPackage(app.targets.arch); - - // AUR gets its special Arch blue, everything else uses category color const hexColor = COLOR_MAP[color] || COLOR_MAP['gray']; const checkboxColor = isAur ? '#1793d1' : hexColor; @@ -82,7 +69,7 @@ export const AppItem = memo(function AppItem({ aria-checked={isSelected} aria-label={`${app.name}${!isAvailable ? ' (unavailable)' : ''}`} aria-disabled={!isAvailable} - className={`app-item group w-full flex items-center gap-2.5 py-1.5 px-2 outline-none transition-all duration-150 + className={`app-item group w-full flex items-center gap-3 py-[7px] px-3 outline-none transition-all duration-150 ${isFocused ? 'bg-[var(--bg-secondary)] border-l-2 shadow-sm' : 'border-l-2 border-transparent'} ${!isAvailable ? 'opacity-40 grayscale-[30%]' @@ -91,7 +78,7 @@ export const AppItem = memo(function AppItem({ style={{ transition: 'background-color 0.15s, color 0.5s', borderColor: isFocused ? hexColor : 'transparent', - backgroundColor: isFocused ? `color-mix(in srgb, ${hexColor}, transparent 85%)` : undefined, // Stronger tint on focus (15% opacity) + backgroundColor: isFocused ? `color-mix(in srgb, ${hexColor}, transparent 85%)` : undefined, '--item-color': hexColor, } as React.CSSProperties} onClick={(e) => { @@ -99,23 +86,17 @@ export const AppItem = memo(function AppItem({ onFocus?.(); if (isAvailable) { onToggle(); - // Umami tracking disabled to save quota - // if (isSelected) { - // analytics.appDeselected(app.name, app.category || '', selectedDistro); - // } else { - // analytics.appSelected(app.name, app.category || '', selectedDistro); - // } } }} >
- {isSelected && } + {isSelected && }
@@ -123,7 +104,8 @@ export const AppItem = memo(function AppItem({ className={`truncate cursor-help ${!isAvailable ? 'text-[var(--text-muted)]' : isSelected ? 'text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'}`} style={{ fontFamily: 'var(--font-open-sans), sans-serif', - fontSize: '16px', + fontSize: '20px', + fontWeight: 500, transition: 'color 0.5s', textRendering: 'geometricPrecision', WebkitFontSmoothing: 'antialiased' @@ -162,7 +144,6 @@ export const AppItem = memo(function AppItem({ )}
- {/* Exclamation mark icon for unavailable apps */} {!isAvailable && (
{ e.stopPropagation(); onTooltipLeave(); }} > = { 'Web Browsers': Globe, 'Communication': MessageCircle, @@ -25,7 +24,6 @@ const CATEGORY_ICONS: Record = { 'System': Cpu, }; -// Tailwind colors as hex const COLOR_MAP: Record = { 'orange': '#f97316', 'blue': '#3b82f6', @@ -44,10 +42,7 @@ const COLOR_MAP: Record = { 'gray': '#6b7280', }; -/** - * Collapsible category header with icon, chevron, and selection badge. - * Uses color-mix for dynamic tinting because we're fancy like that. - */ +// Category header. export function CategoryHeader({ category, isExpanded, @@ -74,33 +69,32 @@ export function CategoryHeader({ tabIndex={-1} aria-expanded={isExpanded} aria-label={`${category} category, ${selectedCount} apps selected`} - // AccessGuide-style: subtle bg with colored left border accent - className={`category-header group w-full h-8 flex items-center gap-2 text-sm font-semibold + className={`category-header group w-full py-2 flex items-center gap-2.5 text-[15px] font-semibold border-l-4 - px-3 mb-3 + px-3 mb-2 transition-all duration-200 outline-none hover:bg-[color-mix(in_srgb,var(--header-color),transparent_80%)]`} style={{ color: 'var(--text-primary)', borderColor: hexColor, backgroundColor: isFocused - ? `color-mix(in srgb, ${hexColor}, transparent 75%)` // 25% opacity for focus - : `color-mix(in srgb, ${hexColor}, transparent 90%)`, // 10% opacity for default + ? `color-mix(in srgb, ${hexColor}, transparent 75%)` + : `color-mix(in srgb, ${hexColor}, transparent 90%)`, '--header-color': hexColor, } as React.CSSProperties} > {(() => { const Icon = CATEGORY_ICONS[category] || Terminal; - return ; + return ; })()} {category} {selectedCount > 0 && ( void; onAppFocus?: (appId: string) => void; - // Flatpak/Snap verification status isVerified?: (distro: DistroId, packageName: string) => boolean; getVerificationSource?: (distro: DistroId, packageName: string) => 'flathub' | 'snap' | null; } -/** - * Color palette for categories. Vibrant ones go to user-facing stuff, - * boring grays go to developer tools because we're used to suffering. - */ const categoryColors: Record = { 'Web Browsers': 'orange', 'Communication': 'blue', @@ -80,10 +71,8 @@ function CategorySectionComponent({ const hasAnimated = useRef(false); const prevAppCount = useRef(categoryApps.length); - // Get color for this category const color = categoryColors[category] || 'gray'; - // Initial entrance animation useLayoutEffect(() => { if (!sectionRef.current || hasAnimated.current) return; hasAnimated.current = true; @@ -92,16 +81,12 @@ function CategorySectionComponent({ const header = section.querySelector('.category-header'); const items = section.querySelectorAll('.app-item'); - // Use requestAnimationFrame for smoother initial setup requestAnimationFrame(() => { - // Initial state with GPU-accelerated transforms gsap.set(header, { clipPath: 'inset(0 100% 0 0)' }); gsap.set(items, { y: -15, opacity: 0, force3D: true }); - // Staggered delay based on category index (reduced for faster feel) const delay = categoryIndex * 0.05; - // Animate header with clip-path reveal gsap.to(header, { clipPath: 'inset(0 0% 0 0)', duration: 0.6, @@ -109,7 +94,6 @@ function CategorySectionComponent({ delay: delay + 0.05 }); - // Animate items with GPU-accelerated transforms gsap.to(items, { y: 0, opacity: 1, @@ -121,11 +105,9 @@ function CategorySectionComponent({ }); }, [categoryIndex]); - // When app count changes (after search clears), ensure all items are visible useEffect(() => { if (categoryApps.length !== prevAppCount.current && sectionRef.current) { const items = sectionRef.current.querySelectorAll('.app-item'); - // Reset any hidden items to visible gsap.set(items, { y: 0, opacity: 1, clearProps: 'all' }); } prevAppCount.current = categoryApps.length; @@ -151,7 +133,7 @@ function CategorySectionComponent({ color={color} />
{categoryApps.map((app) => ( @@ -183,17 +165,13 @@ function CategorySectionComponent({ ); } -// Custom memo comparison - React's shallow compare was killing perf export const CategorySection = memo(CategorySectionComponent, (prevProps, nextProps) => { - // Always re-render if app count changes if (prevProps.categoryApps.length !== nextProps.categoryApps.length) return false; - // Check if app IDs are the same const prevIds = prevProps.categoryApps.map(a => a.id).join(','); const nextIds = nextProps.categoryApps.map(a => a.id).join(','); if (prevIds !== nextIds) return false; - // Check other important props if (prevProps.category !== nextProps.category) return false; if (prevProps.isExpanded !== nextProps.isExpanded) return false; if (prevProps.selectedDistro !== nextProps.selectedDistro) return false; @@ -201,11 +179,9 @@ export const CategorySection = memo(CategorySectionComponent, (prevProps, nextPr if (prevProps.focusedType !== nextProps.focusedType) return false; if (prevProps.categoryIndex !== nextProps.categoryIndex) return false; - // Re-render when verification functions change (Flathub data loads) if (prevProps.isVerified !== nextProps.isVerified) return false; if (prevProps.getVerificationSource !== nextProps.getVerificationSource) return false; - // Check if selection state changed for any app in this category for (const app of nextProps.categoryApps) { if (prevProps.selectedApps.has(app.id) !== nextProps.selectedApps.has(app.id)) { return false; diff --git a/src/components/command/AurDrawerSettings.tsx b/src/components/command/AurDrawerSettings.tsx index 3a06ac1..64aeef8 100644 --- a/src/components/command/AurDrawerSettings.tsx +++ b/src/components/command/AurDrawerSettings.tsx @@ -8,12 +8,7 @@ interface AurDrawerSettingsProps { setSelectedHelper: (helper: 'yay' | 'paru') => void; } -/** - * AUR package settings panel for Arch users. - * Lets you pick between yay and paru, and whether to install the helper. - * The naming of hasYayInstalled is a bit misleading - it actually means - * "user already has an AUR helper" regardless of which one. Tech debt, I know. - */ +// AUR package settings panel. export function AurDrawerSettings({ aurAppNames, hasYayInstalled, @@ -23,8 +18,8 @@ export function AurDrawerSettings({ distroColor, }: AurDrawerSettingsProps & { distroColor: string }) { return ( -
-
+
+
AUR Packages: {aurAppNames.join(', ')}
@@ -32,7 +27,7 @@ export function AurDrawerSettings({
AUR Helper -
+
- {/* Buttons */}
- {/* Hint */}

Change anytime in preview window @@ -193,23 +176,19 @@ export function AurFloatingCard({

- {/* Card 2: Which helper? (appears after first answer) - AccessGuide style */} {hasAnswered !== null && (
- {/* Header */}

{hasAnswered @@ -226,16 +205,15 @@ export function AurFloatingCard({

- {/* Helper selection */}
-
+
{showAur && ( )} - {/* Nix unfree packages warning */} {isNix && hasUnfreePackages && (
@@ -177,25 +173,28 @@ export function CommandDrawer({
)} - {/* Terminal preview - where the magic gets displayed */} -
-
+
+
{isNix ? 'nix' : 'bash'} {/* Desktop action buttons */}
- {/* Command text */}
selectedCount > 0 && setDrawerOpen(true)} + onClick={() => { + if (selectedCount > 0) { + if (externalOnDrawerOpen) { + externalOnDrawerOpen(); + } else { + setInternalDrawerOpen(true); + } + } + }} > 0 ? 'text-[var(--text-primary)]' : 'text-[var(--text-muted)]'}`}> {command}
- {/* Clear button (hidden on mobile) */} - {/* Download button (hidden on mobile) */} - {/* Copy button (hidden on mobile) */}
- {/* Content */}
- {/* Shortcuts - AccessGuide style */}
-

Keyboard Shortcuts

+

Keyboard Shortcuts

{[ ['↑↓←→', 'Navigate through apps'], @@ -148,7 +132,7 @@ export function HowItWorks() { ['1 / 2', 'Switch AUR helper (yay/paru)'], ].map(([key, desc]) => (
- + {key} {desc} @@ -157,9 +141,8 @@ export function HowItWorks() {
- {/* Getting Started - AccessGuide style */}
-

Getting Started

+

Getting Started

  1. 1. Pick your distro — Select your Linux distribution from the dropdown at the top. This determines which package manager commands TuxMate generates for you. @@ -176,9 +159,8 @@ export function HowItWorks() {
- {/* Notes - AccessGuide style */}
-

Good to Know

+

Good to Know

  • Greyed out apps aren't available in your distro's official repositories. Try switching to Flatpak or Snap in the dropdown, or hover the info icon next to the app for alternative installation methods. @@ -196,7 +178,7 @@ export function HowItWorks() { NixOS — Generates `environment.systemPackages`. If you pick unfree apps, the download includes comments showing exactly what to whitelist with `allowUnfree`.
  • - Script Safety — Downloaded scripts are robust and idempotent. They include error handling, network retries, and system checks. Run them with bash tuxmate-*.sh to safely install your selection. + Script Safety — Downloaded scripts are robust and idempotent. They include error handling, network retries, and system checks. Run them with bash tuxmate-*.sh to safely install your selection.
diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx new file mode 100644 index 0000000..3510ea8 --- /dev/null +++ b/src/components/sidebar/Sidebar.tsx @@ -0,0 +1,450 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { Check, Copy, Download, X, Search, ChevronDown, Github, Heart, Eye, Terminal, Trash2 } from 'lucide-react'; +import { distros, type DistroId } from '@/lib/data'; +import { generateInstallScript } from '@/lib/generateInstallScript'; +import { analytics } from '@/lib/analytics'; +import { ThemeToggle } from '@/components/ui/theme-toggle'; +import { DistroIcon } from '@/components/distro/DistroIcon'; +import { HowItWorks } from '@/components/header/HowItWorks'; + +interface SidebarProps { + selectedDistro: DistroId; + onDistroSelect: (id: DistroId) => void; + selectedApps: Set; + selectedCount: number; + clearAll: () => void; + command: string; + searchQuery: string; + onSearchChange: (query: string) => void; + searchInputRef: React.RefObject; + hasAurPackages: boolean; + aurAppNames: string[]; + selectedHelper: 'yay' | 'paru'; + setSelectedHelper: (helper: 'yay' | 'paru') => void; + hasUnfreePackages?: boolean; + unfreeAppNames?: string[]; + onOpenDrawer: () => void; + activeShortcut?: string | null; +} + +export function Sidebar({ + selectedDistro, + onDistroSelect, + selectedApps, + selectedCount, + clearAll, + command, + searchQuery, + onSearchChange, + searchInputRef, + hasAurPackages, + aurAppNames, + selectedHelper, + setSelectedHelper, + hasUnfreePackages, + unfreeAppNames, + onOpenDrawer, + activeShortcut, +}: SidebarProps) { + const [copied, setCopied] = useState(false); + const [distroOpen, setDistroOpen] = useState(false); + + const showAur = selectedDistro === 'arch' && hasAurPackages; + const currentDistro = distros.find(d => d.id === selectedDistro); + const distroColor = currentDistro?.color || 'var(--accent)'; + + const handleCopy = useCallback(async () => { + if (selectedCount === 0) return; + await navigator.clipboard.writeText(command); + setCopied(true); + const distroName = distros.find(d => d.id === selectedDistro)?.name || selectedDistro; + analytics.commandCopied(distroName, selectedCount); + setTimeout(() => setCopied(false), 3000); + }, [command, selectedCount, selectedDistro]); + + const handleDownload = useCallback(() => { + if (selectedCount === 0) return; + const script = generateInstallScript({ + distroId: selectedDistro, + selectedAppIds: selectedApps, + helper: selectedHelper, + }); + const isNix = selectedDistro === 'nix'; + const mimeType = isNix ? 'text/plain' : 'text/x-shellscript'; + const blob = new Blob([script], { type: mimeType }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = isNix ? 'configuration.nix' : `tuxmate-${selectedDistro}.sh`; + a.click(); + setTimeout(() => URL.revokeObjectURL(url), 1000); + const distroName = distros.find(d => d.id === selectedDistro)?.name || selectedDistro; + analytics.scriptDownloaded(distroName, selectedCount); + }, [selectedCount, selectedDistro, selectedApps, selectedHelper]); + + const handleSearchKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape' || e.key === 'Enter') { + e.preventDefault(); + (e.target as HTMLInputElement).blur(); + } + }; + + return ( + + ); +} diff --git a/src/components/sidebar/index.ts b/src/components/sidebar/index.ts new file mode 100644 index 0000000..4e85fd4 --- /dev/null +++ b/src/components/sidebar/index.ts @@ -0,0 +1,3 @@ +// Sidebar components + +export { Sidebar } from './Sidebar'; diff --git a/src/components/ui/theme-toggle.tsx b/src/components/ui/theme-toggle.tsx index 051a220..cd196b9 100644 --- a/src/components/ui/theme-toggle.tsx +++ b/src/components/ui/theme-toggle.tsx @@ -14,11 +14,6 @@ export function ThemeToggle({ className }: ThemeToggleProps) { const { theme, toggle } = useTheme() const [mounted, setMounted] = useState(false) - /** - * Classic hydration mismatch avoidance. Server has no idea what - * localStorage says, so we render a placeholder first. - * Yes, there's probably a better way. No, I don't want to hear about it. - */ useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect setMounted(true) @@ -26,12 +21,11 @@ export function ThemeToggle({ className }: ThemeToggleProps) { const isDark = theme === "dark" - // Render placeholder with same dimensions during SSR if (!mounted) { return (
void, @@ -25,29 +23,24 @@ export function useKeyboardNavigation( ) { const [focusPos, setFocusPos] = useState(null); - // Track if focus was set via keyboard (to enable scroll) vs mouse (no scroll) const fromKeyboard = useRef(false); - // Track if focus mode is keyboard (for UI highlighting) const [isKeyboardNavigating, setIsKeyboardNavigating] = useState(false); - /** Clear focus (e.g., when clicking outside) */ const clearFocus = useCallback(() => setFocusPos(null), []); - /** Get the currently focused item */ const focusedItem = useMemo(() => { if (!focusPos) return null; return navItems[focusPos.col]?.[focusPos.row] || null; }, [navItems, focusPos]); - /** Set focus position by item type and id (from mouse - no scroll) */ const setFocusByItem = useCallback((type: 'category' | 'app', id: string) => { for (let col = 0; col < navItems.length; col++) { const colItems = navItems[col]; for (let row = 0; row < colItems.length; row++) { if (colItems[row].type === type && colItems[row].id === id) { - fromKeyboard.current = false; // Mouse selection - don't scroll - setIsKeyboardNavigating(false); // Disable focus ring + fromKeyboard.current = false; + setIsKeyboardNavigating(false); setFocusPos({ col, row }); return; } @@ -55,18 +48,14 @@ export function useKeyboardNavigation( } }, [navItems]); - /** Keyboard event handler */ useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - // Skip if typing in input if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; - // Skip if modifier keys are pressed (prevents conflicts with browser shortcuts like Ctrl+D) if (e.ctrlKey || e.altKey || e.metaKey) return; const key = e.key; - // Space to toggle if (key === ' ') { e.preventDefault(); if (focusPos) { @@ -77,21 +66,17 @@ export function useKeyboardNavigation( return; } - // Navigation keys (arrow keys + vim keys) if (!['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'j', 'k', 'h', 'l', 'Escape'].includes(key)) return; e.preventDefault(); - // Escape clears focus if (key === 'Escape') { setFocusPos(null); return; } - // Mark as keyboard navigation - will trigger scroll and focus ring fromKeyboard.current = true; setIsKeyboardNavigating(true); - // Navigate setFocusPos(prev => { if (!prev) return { col: 0, row: 0 }; @@ -129,14 +114,12 @@ export function useKeyboardNavigation( return () => window.removeEventListener('keydown', handleKeyDown); }, [navItems, focusPos, onToggleCategory, onToggleApp]); - /* Scroll focused item into view - only when navigating via keyboard */ useEffect(() => { if (!focusPos || !fromKeyboard.current) return; const item = navItems[focusPos.col]?.[focusPos.row]; if (!item) return; - // Find visible element among duplicates (mobile/desktop layouts both render same data-nav-id) const elements = document.querySelectorAll( `[data-nav-id="${item.type}:${item.id}"]` ); diff --git a/src/hooks/useLinuxInit.ts b/src/hooks/useLinuxInit.ts index 520e84f..5a3ecfb 100644 --- a/src/hooks/useLinuxInit.ts +++ b/src/hooks/useLinuxInit.ts @@ -5,11 +5,7 @@ import { distros, apps, type DistroId } from '@/lib/data'; import { isAurPackage } from '@/lib/aur'; import { isUnfreePackage } from '@/lib/nixUnfree'; -// Re-export for backwards compatibility export { isAurPackage, AUR_PATTERNS, KNOWN_AUR_PACKAGES } from '@/lib/aur'; - -// Everything the app needs to work - export interface UseLinuxInitReturn { selectedDistro: DistroId; selectedApps: Set; @@ -22,7 +18,6 @@ export interface UseLinuxInitReturn { generatedCommand: string; selectedCount: number; availableCount: number; - // Arch/AUR specific hasYayInstalled: boolean; setHasYayInstalled: (value: boolean) => void; selectedHelper: 'yay' | 'paru'; @@ -30,10 +25,8 @@ export interface UseLinuxInitReturn { hasAurPackages: boolean; aurPackageNames: string[]; aurAppNames: string[]; - // Nix unfree specific hasUnfreePackages: boolean; unfreeAppNames: string[]; - // Hydration state isHydrated: boolean; } @@ -49,7 +42,6 @@ export function useLinuxInit(): UseLinuxInitReturn { const [selectedHelper, setSelectedHelper] = useState<'yay' | 'paru'>('yay'); const [hydrated, setHydrated] = useState(false); - // Load saved preferences from localStorage useEffect(() => { try { const savedDistro = localStorage.getItem(STORAGE_KEY_DISTRO) as DistroId | null; @@ -64,7 +56,6 @@ export function useLinuxInit(): UseLinuxInitReturn { if (savedApps) { const appIds = JSON.parse(savedApps) as string[]; - // Filter to only valid app IDs that are available on the distro const validApps = appIds.filter(id => { const app = apps.find(a => a.id === id); if (!app) return false; @@ -82,12 +73,10 @@ export function useLinuxInit(): UseLinuxInitReturn { setSelectedHelper('paru'); } } catch { - // Ignore localStorage errors } setHydrated(true); }, []); - // Save to localStorage whenever state changes (but not on first render) useEffect(() => { if (!hydrated) return; try { @@ -96,11 +85,9 @@ export function useLinuxInit(): UseLinuxInitReturn { localStorage.setItem(STORAGE_KEY_YAY, hasYayInstalled.toString()); localStorage.setItem(STORAGE_KEY_HELPER, selectedHelper); } catch { - // Ignore localStorage errors } }, [selectedDistro, selectedApps, hasYayInstalled, selectedHelper, hydrated]); - // Compute AUR package info for Arch const aurPackageInfo = useMemo(() => { if (selectedDistro !== 'arch') { return { hasAur: false, packages: [] as string[], appNames: [] as string[] }; @@ -122,7 +109,6 @@ export function useLinuxInit(): UseLinuxInitReturn { return { hasAur: aurPkgs.length > 0, packages: aurPkgs, appNames: aurAppNames }; }, [selectedDistro, selectedApps]); - // Compute unfree package info for Nix const unfreePackageInfo = useMemo(() => { if (selectedDistro !== 'nix') { return { hasUnfree: false, appNames: [] as string[] }; @@ -173,7 +159,6 @@ export function useLinuxInit(): UseLinuxInitReturn { }, []); const toggleApp = useCallback((appId: string) => { - // Check availability inline to avoid stale closure const app = apps.find(a => a.id === appId); if (!app) return; const pkg = app.targets[selectedDistro]; @@ -230,7 +215,6 @@ export function useLinuxInit(): UseLinuxInitReturn { if (packageNames.length === 0) return '# No packages selected'; - // Nix: show declarative config (no unfree warning in preview - that's in download) if (selectedDistro === 'nix') { const sortedPkgs = packageNames.filter(p => p.trim()).sort(); const pkgList = sortedPkgs.map(p => ` ${p}`).join('\n'); @@ -238,36 +222,26 @@ export function useLinuxInit(): UseLinuxInitReturn { } if (selectedDistro === 'snap') { - // Snap needs separate commands for --classic packages if (packageNames.length === 1) { return `${distro.installPrefix} ${packageNames[0]}`; } - // For multiple snap packages, we chain them with && - // Note: snap doesn't support installing multiple packages in one command like apt return packageNames.map(p => `sudo snap install ${p}`).join(' && '); } - // Arch with AUR packages - this is where it gets fun if (selectedDistro === 'arch' && aurPackageInfo.hasAur) { if (!hasYayInstalled) { - // User doesn't have current helper installed - prepend installation const helperName = selectedHelper; // yay or paru - // Common setup: sudo pacman -S --needed git base-devel - // Then clone, make, install const installHelperCmd = `sudo pacman -S --needed git base-devel && git clone https://aur.archlinux.org/${helperName}.git /tmp/${helperName} && cd /tmp/${helperName} && makepkg -si --noconfirm && cd - && rm -rf /tmp/${helperName}`; - // Install packages using the helper const installCmd = `${helperName} -S --needed --noconfirm ${packageNames.join(' ')}`; return `${installHelperCmd} && ${installCmd}`; } else { - // User has helper installed - use it for ALL packages return `${selectedHelper} -S --needed --noconfirm ${packageNames.join(' ')}`; } } - // Handle Homebrew: separate formulae and casks into separate commands if (selectedDistro === 'homebrew') { const formulae = packageNames.filter(p => !p.startsWith('--cask ')); const casks = packageNames.filter(p => p.startsWith('--cask ')).map(p => p.replace('--cask ', '')); @@ -296,7 +270,6 @@ export function useLinuxInit(): UseLinuxInitReturn { generatedCommand, selectedCount: selectedApps.size, availableCount, - // Arch/AUR specific hasYayInstalled, setHasYayInstalled, selectedHelper, @@ -304,10 +277,8 @@ export function useLinuxInit(): UseLinuxInitReturn { hasAurPackages: aurPackageInfo.hasAur, aurPackageNames: aurPackageInfo.packages, aurAppNames: aurPackageInfo.appNames, - // Nix unfree specific hasUnfreePackages: unfreePackageInfo.hasUnfree, unfreeAppNames: unfreePackageInfo.appNames, - // Hydration state isHydrated: hydrated, }; } diff --git a/src/hooks/useTheme.tsx b/src/hooks/useTheme.tsx index 1f5582e..022d81a 100644 --- a/src/hooks/useTheme.tsx +++ b/src/hooks/useTheme.tsx @@ -11,17 +11,13 @@ interface ThemeContextType { const ThemeContext = createContext(undefined) -/** - * Theme provider that syncs with localStorage and system preferences. - * Also handles the initial hydration dance to avoid theme flash. - */ +// Theme provider. export function ThemeProvider({ children }: { children: React.ReactNode }) { - // Initial state reads from DOM to match what the inline script set const [theme, setTheme] = useState(() => { if (typeof window !== 'undefined') { return document.documentElement.classList.contains('light') ? 'light' : 'dark' } - return 'light' // SSR default + return 'light' }) const [hydrated, setHydrated] = useState(false) diff --git a/src/hooks/useTooltip.ts b/src/hooks/useTooltip.ts index 0f179c8..19cb3a7 100644 --- a/src/hooks/useTooltip.ts +++ b/src/hooks/useTooltip.ts @@ -8,12 +8,7 @@ export interface TooltipState { y: number; } -/** - * Tooltip that stays open while hovering trigger or tooltip. - * - 450ms delay before showing - * - Stays open once shown (until mouse leaves both trigger and tooltip) - * - Dismiss on click/scroll/escape - */ +// Tooltip hook. export function useTooltip() { const [tooltip, setTooltip] = useState(null); const showTimeout = useRef(null); @@ -22,7 +17,6 @@ export function useTooltip() { const isOverTooltip = useRef(false); const tooltipRef = useRef(null); - // Allow setting the tooltip element ref from the Tooltip component const setTooltipRef = useCallback((el: HTMLDivElement | null) => { tooltipRef.current = el; }, []); @@ -40,7 +34,6 @@ export function useTooltip() { const tryHide = useCallback(() => { cancel(); - // Only hide if mouse is not over trigger or tooltip hideTimeout.current = setTimeout(() => { if (!isOverTrigger.current && !isOverTooltip.current) { setTooltip(null); @@ -54,13 +47,15 @@ export function useTooltip() { cancel(); const rect = target.getBoundingClientRect(); - const clientX = e.clientX; // Capture mouse X position + const clientX = e.clientX; + + const zoom = parseFloat(getComputedStyle(document.documentElement).zoom) || 1; showTimeout.current = setTimeout(() => { setTooltip({ content, - x: clientX, // Use mouse X instead of element center - y: rect.top, + x: clientX / zoom, + y: rect.top / zoom, }); }, 450); }, [cancel]); @@ -82,7 +77,6 @@ export function useTooltip() { useEffect(() => { const dismiss = (e: MouseEvent) => { - // Don't dismiss if clicking inside the tooltip if (tooltipRef.current && tooltipRef.current.contains(e.target as Node)) { return; } diff --git a/src/hooks/useVerification.ts b/src/hooks/useVerification.ts index 03b0aef..14abe24 100644 --- a/src/hooks/useVerification.ts +++ b/src/hooks/useVerification.ts @@ -8,16 +8,13 @@ import { } from '@/lib/verification'; export interface UseVerificationResult { - // Kept for compatibility, always false now isLoading: boolean; hasError: boolean; isVerified: (distro: DistroId, packageName: string) => boolean; getVerificationSource: (distro: DistroId, packageName: string) => 'flathub' | 'snap' | null; } -// Now purely synchronous using build-time generated data export function useVerification(): UseVerificationResult { - // Check if package is verified for the distro const isVerified = useCallback((distro: DistroId, packageName: string): boolean => { if (distro === 'flatpak') { return isFlathubVerified(packageName); @@ -28,7 +25,6 @@ export function useVerification(): UseVerificationResult { return false; }, []); - // Get verification source for badge styling const getVerificationSource = useCallback((distro: DistroId, packageName: string): 'flathub' | 'snap' | null => { if (distro === 'flatpak' && isFlathubVerified(packageName)) { return 'flathub'; diff --git a/src/lib/verified-flatpaks.json b/src/lib/verified-flatpaks.json index f2f3290..011c47c 100644 --- a/src/lib/verified-flatpaks.json +++ b/src/lib/verified-flatpaks.json @@ -1,66 +1,137 @@ { - "fetchedAt": "2026-01-25T17:01:05.599Z", - "count": 303, + "meta": { + "fetchedAt": "2026-02-22T00:43:13.504Z" + }, + "count": 697, "apps": [ + "ai.jan.Jan", + "app.bluebubbles.BlueBubbles", + "app.comaps.comaps", + "app.devsuite.Ptyxis", + "app.drey.Dialect", + "app.drey.EarTag", "app.drey.Warp", + "app.eduroam.geteduroam", + "app.fotema.Fotema", + "app.grayjay.Grayjay", + "app.opencomic.OpenComic", "app.organicmaps.desktop", "app.polychromatic.controller", + "app.tintero.Tintero", "app.twintaillauncher.ttl", "app.xemu.xemu", "app.zen_browser.zen", + "at.vintagestory.VintageStory", "be.alexandervanhee.gradia", "best.ellie.StartupConfiguration", + "br.com.wiselabs.simplexity", + "br.eng.silas.qpdftools", + "ca.desrt.dconf-editor", + "ca.edestcroix.Recordbox", "ca.parallel_launcher.ParallelLauncher", + "cafe.avery.Delfin", "ch.tlaun.TL", + "chat.delta.desktop", + "chat.simplex.simplex", + "cn.xfangfang.wiliwili", + "com.abisource.AbiWord", "com.actualbudget.actual", "com.adamcake.Bolt", + "com.adilhanney.saber", "com.atlauncher.ATLauncher", "com.bambulab.BambuStudio", + "com.beavernotes.beavernotes", + "com.belmoussaoui.Authenticator", + "com.belmoussaoui.Decoder", + "com.belmoussaoui.Obfuscate", + "com.bilingify.readest", "com.bitwarden.desktop", "com.bitwig.BitwigStudio", + "com.blitterstudio.amiberry", + "com.borgbase.Vorta", + "com.boxy_svg.BoxySVG", "com.brave.Browser", + "com.cassidyjames.butler", + "com.chatterino.chatterino", "com.collaboraoffice.Office", + "com.core447.StreamController", + "com.daniel15.wcc", "com.dec05eba.gpu_screen_recorder", + "com.devolutions.remotedesktopmanager", "com.discordapp.Discord", + "com.endlessm.photos", + "com.expidusos.file_manager", + "com.fastmail.Fastmail", + "com.feaneron.Boatswain", "com.freerdp.FreeRDP", "com.geeks3d.furmark", + "com.github.ADBeveridge.Raider", + "com.github.Darazaki.Spedread", "com.github.IsmaelMartinez.teams_for_linux", "com.github.KRTirtho.Spotube", "com.github.Matoking.protontricks", + "com.github.Murmele.Gittyup", "com.github.PintaProject.Pinta", "com.github.Rosalie241.RMG", "com.github.d4nj1.tlpui", "com.github.dail8859.NotepadNext", + "com.github.dynobo.normcap", "com.github.finefindus.eyedropper", "com.github.flxzt.rnote", + "com.github.gabutakut.gabutdm", + "com.github.geigi.cozy", "com.github.hluk.copyq", + "com.github.hugolabe.Wike", + "com.github.huluti.Curtail", "com.github.iwalton3.jellyfin-media-player", + "com.github.iwalton3.jellyfin-mpv-shim", "com.github.jeromerobert.pdfarranger", "com.github.jkotra.eovpn", "com.github.johnfactotum.Foliate", "com.github.joseexposito.touche", "com.github.louis77.tuner", "com.github.maoschanz.drawing", + "com.github.marhkb.Pods", "com.github.mtkennerly.ludusavi", "com.github.neithern.g4music", "com.github.phase1geo.minder", + "com.github.polymeilex.neothesia", "com.github.qarmin.czkawka", "com.github.rafostar.Clapper", + "com.github.ryonakano.pinit", + "com.github.ryonakano.reco", "com.github.sdv43.whaler", + "com.github.skylot.jadx", "com.github.taiko2k.tauonmb", "com.github.tchx84.Flatseal", "com.github.tenderowl.frog", "com.github.unrud.VideoDownloader", + "com.github.unrud.djpdf", + "com.github.vikdevelop.photopea_app", + "com.github.vikdevelop.timer", + "com.github.vkohaupt.vokoscreenNG", + "com.github.vladimiry.ElectronMail", "com.github.wwmm.easyeffects", "com.github.wwmm.pulseeffects", "com.github.xournalpp.xournalpp", "com.github.zocker_160.SyncThingy", "com.github.ztefn.haguichi", + "com.gitlab.bitseater.meteo", + "com.gopeed.Gopeed", "com.heroicgameslauncher.hgl", + "com.icons8.Lunacy", + "com.indomitusgroup.indipdf", + "com.infinipaint.infinipaint", + "com.interversehq.qView", "com.jeffser.Alpaca", + "com.jwestall.Forecast", + "com.kgurgul.cpuinfo", + "com.ktechpit.colorwall", "com.ktechpit.orion", + "com.ktechpit.torrhunt", "com.ktechpit.ultimate-media-downloader", "com.ktechpit.whatsie", + "com.ktechpit.wonderwall", "com.logseq.Logseq", "com.lunarclient.LunarClient", "com.markopejic.downloader", @@ -69,142 +140,355 @@ "com.ml4w.dotfilesinstaller", "com.modrinth.ModrinthApp", "com.moonlight_stream.Moonlight", + "com.neatdecisions.Detwinner", "com.notesnook.Notesnook", + "com.ntrack.n-track", "com.obsproject.Studio", + "com.odnoyko.valot", + "com.pikatorrent.PikaTorrent", "com.play0ad.zeroad", "com.plexamp.Plexamp", + "com.pojtinger.felicitas.Sessions", "com.pokemmo.PokeMMO", "com.prusa3d.PrusaSlicer", "com.rafaelmardojai.Blanket", + "com.raggesilver.BlackBox", "com.ranfdev.DistroShelf", "com.rcloneui.RcloneUI", + "com.realm667.Wolfenstein_Blade_of_Agony", + "com.redis.RedisInsight", "com.rtosta.zapzap", "com.rustdesk.RustDesk", + "com.saivert.pwvucontrol", + "com.sidevesh.Luminance", + "com.steamgriddb.SGDBoop", + "com.steamgriddb.steam-rom-manager", + "com.stremio.Service", "com.stremio.Stremio", + "com.super_productivity.SuperProductivity", "com.surfshark.Surfshark", "com.thincast.client", "com.tomjwatson.Emote", + "com.toolstack.Folio", + "com.ulaa.Ulaa", + "com.unicornsonlsd.finamp", "com.usebottles.bottles", "com.usebruno.Bruno", "com.valvesoftware.SteamLink", "com.vixalien.sticky", + "com.voxdsp.TuxFishing", "com.vscodium.codium", + "com.vscodium.codium-insiders", "com.vysp3r.ProtonPlus", + "com.warlordsoftwares.formatlab", "com.warlordsoftwares.media-downloader", "com.warlordsoftwares.tube2go", "com.warlordsoftwares.youtube-downloader-4ktube", + "com.zettlr.Zettlr", + "de.capypara.FieldMonitor", + "de.haeckerfelix.AudioSharing", "de.haeckerfelix.Fragments", "de.haeckerfelix.Shortwave", + "de.k_bo.Televido", + "de.leopoldluley.Clapgrep", + "de.mediathekview.MediathekView", + "de.schmidhuberj.DieBahn", + "de.schmidhuberj.tubefeeder", + "de.z_ray.OptimusUI", + "dev.bragefuglseth.Fretboard", "dev.bragefuglseth.Keypunch", "dev.deedles.Trayscale", + "dev.dergs.Tonearm", + "dev.diegovsky.Riff", + "dev.edfloreshz.Calculator", "dev.edfloreshz.CosmicTweaks", + "dev.edfloreshz.Tasks", "dev.fredol.open-tv", + "dev.ftb.ftb-app", + "dev.geopjr.Collision", + "dev.geopjr.Tuba", "dev.goats.xivlauncher", + "dev.heppen.webapps", + "dev.ibrahimcetin.reins", + "dev.lapce.lapce", + "dev.lasheen.qr", "dev.lizardbyte.app.Sunshine", + "dev.mufeed.Wordbook", "dev.qwery.AddWater", + "dev.ters.LocalTranslate", "dev.vencord.Vesktop", + "dk.gqrx.gqrx", "eu.betterbird.Betterbird", + "eu.ithz.umftpd", + "eu.jumplink.Learn6502", + "eu.nokun.MirrorHall", "fr.handbrake.ghb", + "garden.jamie.Morphosis", + "gg.minion.Minion", + "gg.norisk.NoRiskClientLauncherV3", "hu.irl.cameractrls", + "im.bernard.Nostalgia", + "im.dino.Dino", + "im.fluffychat.Fluffychat", + "im.nheko.Nheko", + "in.cinny.Cinny", + "info.beyondallreason.bar", + "info.bibletime.BibleTime", "info.cemu.Cemu", "info.febvre.Komikku", + "info.mumble.Mumble", + "info.portfolio_performance.PortfolioPerformance", "info.smplayer.SMPlayer", + "ink.whis.Whis", + "io.anytype.anytype", + "io.appflowy.AppFlowy", "io.bassi.Amberol", + "io.emeric.toolblex", + "io.ente.auth", + "io.ente.photos", + "io.frama.tractor.carburetor", "io.freetubeapp.FreeTube", "io.gdevelop.ide", + "io.github.BrisklyDev.Brisk", + "io.github.CyberTimon.RapidRAW", + "io.github.DenysMb.Kontainer", + "io.github.Ethanscharlie.albumripper", "io.github.Faugus.faugus-launcher", "io.github.Foldex.AdwSteamGtk", + "io.github.Geocld.XStreamingDesktop", "io.github.IshuSinghSE.aurynk", "io.github.JakubMelka.Pdf4qt", + "io.github.N3kosempai.hetairos-ai", + "io.github.N3kosempai.klia-store", + "io.github.Predidit.Kazumi", + "io.github.Qalculate", + "io.github.Qalculate.qalculate-qt", "io.github.Soundux", + "io.github.TheWisker.Cavasik", "io.github.aandrew_me.ytdn", "io.github.alainm23.planify", + "io.github.alescdb.mailviewer", + "io.github.alfianlosari.GTKChatGPT", "io.github.amit9838.mousam", "io.github.antimicrox.antimicrox", "io.github.arunsivaramanneo.GPUViewer", "io.github.astralvixen.geforce-infinity", "io.github.benjamimgois.goverlay", + "io.github.brunofin.Cohesion", + "io.github.bytezz.IPLookup", + "io.github.cboxdoerfer.FSearch", "io.github.celluloid_player.Celluloid", + "io.github.cosmic_utils.Examine", "io.github.cosmic_utils.camera", + "io.github.davidoc26.wallpaper_selector", "io.github.debasish_patra_1987.linuxthemestore", + "io.github.diegopvlk.Cine", + "io.github.diegopvlk.Tomatillo", + "io.github.dimtpap.coppwr", + "io.github.dosbox-staging", + "io.github.dubstar_04.design", "io.github.dvlv.boxbuddyrs", + "io.github.dyegoaurelio.simple-wireplumber-gui", + "io.github.ebonjaeger.bluejay", + "io.github.ecotubehq.player", + "io.github.efogdev.mpris-timer", + "io.github.endless_sky.endless_sky", + "io.github.fabrialberio.pinapp", "io.github.fastrizwaan.WineCharm", "io.github.fastrizwaan.WineZGUI", + "io.github.ferraridamiano.ConverterNOW", "io.github.flattool.Ignition", "io.github.flattool.Warehouse", + "io.github.gaheldev.Millisecond", + "io.github.gamingdoom.Datcord", "io.github.getnf.embellish", "io.github.giantpinkrobots.bootqt", "io.github.giantpinkrobots.flatsweep", "io.github.giantpinkrobots.varia", + "io.github.gopher64.gopher64", + "io.github.hedge_dev.hedgemodmanager", + "io.github.hkdb.Aerion", + "io.github.htkhiem.Euphonica", "io.github.ilya_zlobintsev.LACT", "io.github.jeffshee.Hidamari", + "io.github.jliljebl.Flowblade", "io.github.jonmagon.kdiskmark", + "io.github.jorchube.monitorets", + "io.github.josephmawa.Gauge", "io.github.kolunmi.Bazaar", + "io.github.kukuruzka165.materialgram", + "io.github.ladaapp.lada", + "io.github.lainsce.Notejot", + "io.github.lawstorant.boxflat", + "io.github.libvibrant.vibrantLinux", + "io.github.limo_app.limo", "io.github.linx_systems.ClamUI", + "io.github.mandruis7.xbox-cloud-gaming-electron", + "io.github.marco_calautti.DeltaPatcher", + "io.github.martchus.syncthingtray", + "io.github.martinrotter.rssguard", + "io.github.mfat.sshpilot", + "io.github.mfat.tvhplayer", "io.github.mhogomchungu.media-downloader", + "io.github.milkshiift.GoofCord", + "io.github.mpc_qt.mpc-qt", + "io.github.mpobaschnig.Vaults", + "io.github.mrvladus.List", + "io.github.nacho.mecalin", + "io.github.nokse22.Exhibit", + "io.github.nokse22.asciidraw", + "io.github.nokse22.high-tide", + "io.github.nokse22.inspector", "io.github.nozwock.Packet", + "io.github.nroduit.Weasis", + "io.github.nuttyartist.notes", + "io.github.olaproeis.Ferrite", + "io.github.onionware_github.onionmedia", + "io.github.pantheon_tweaks.pantheon-tweaks", "io.github.peazip.PeaZip", + "io.github.pieterdd.RcloneShuttle", + "io.github.plrigaux.sysd-manager", + "io.github.pol_rivero.github-desktop-plus", "io.github.prateekmedia.appimagepool", + "io.github.prateekmedia.pstube", + "io.github.quodlibet.QuodLibet", "io.github.qwersyk.Newelle", "io.github.radiolamp.mangojuice", "io.github.realmazharhussain.GdmSettings", "io.github.revisto.drum-machine", + "io.github.rfrench3.scopebuddy-gui", + "io.github.schwarzen.colormydesktop", "io.github.seadve.Kooha", + "io.github.seadve.Mousai", + "io.github.sepehr_rs.Sudoku", + "io.github.sharkwouter.Minigalaxy", "io.github.shiiion.primehack", "io.github.shonebinu.Brief", + "io.github.shonubot.Spruce", + "io.github.sigmasd.stimulator", + "io.github.sitraorg.sitra", "io.github.streetpea.Chiaki4deck", + "io.github.swordpuffin.rewaita", + "io.github.swordpuffin.wardrobe", + "io.github.teacond.Morse", "io.github.thetumultuousunicornofdarkness.cpu-x", + "io.github.tntwise.REAL-Video-Enhancer", "io.github.tobagin.karere", + "io.github.tobagin.scramble", + "io.github.totoshko88.RustConn", + "io.github.troyeguo.koodo-reader", "io.github.ungoogled_software.ungoogled_chromium", "io.github.unknownskl.greenlight", + "io.github.v81d.Wattage", + "io.github.vikdevelop.SaveDesktop", + "io.github.vmkspv.lenspect", + "io.github.wartybix.Constrict", "io.github.wiiznokes.fan-control", "io.github.wivrn.wivrn", + "io.github.xyproto.zsnes", + "io.github.yairm210.unciv", + "io.github.zaedus.spider", "io.github.zarestia_dev.rclone-manager", + "io.github.zingytomato.netpeek", + "io.gitlab.Goodvibes", "io.gitlab.adhami3310.Converter", + "io.gitlab.adhami3310.Footage", "io.gitlab.adhami3310.Impression", "io.gitlab.librewolf-community", "io.gitlab.news_flash.NewsFlash", "io.gitlab.theevilskeleton.Upscaler", + "io.kapsa.drive", + "io.kinvolk.Headlamp", + "io.m51.Gelly", "io.missioncenter.MissionCenter", + "io.openrct2.OpenRCT2", "io.podman_desktop.PodmanDesktop", + "io.qt.QtCreator", + "io.sourceforge.pysolfc.PySolFC", + "it.dottorblaster.cauldron", + "it.fabiodistasio.AntaresSQL", "it.mijorus.gearlever", + "it.mijorus.smile", + "it.mijorus.whisper", + "it.mq1.TinyWiiBackupManager", "jp.nonbili.noutube", "md.obsidian.Obsidian", + "me.ahola.aphototoollibre", + "me.amankhanna.opendeck", "me.iepure.devtoolbox", + "me.timschneeberger.GalaxyBudsClient", "me.timschneeberger.jdsp4linux", "moe.launcher.an-anime-game-launcher", + "moe.launcher.sleepy-launcher", + "moe.launcher.the-honkers-railway-launcher", "net.codelogistics.webapps", "net.davidotek.pupgui2", + "net.fasterland.converseen", + "net.fhannenheim.musicfetch", + "net.giuspen.cherrytree", + "net.jami.Jami", "net.kuribo64.melonDS", "net.lutris.Lutris", + "net.mkiol.Jupii", "net.mkiol.SpeechNote", "net.nokyan.Resources", "net.nymtech.NymVPN", + "net.openra.OpenRA", "net.pcsx2.PCSX2", "net.retrodeck.retrodeck", + "net.runelite.RuneLite", + "net.sapples.LiveCaptions", "net.shadps4.shadPS4", + "net.sourceforge.VMPK", + "net.sourceforge.m64py.M64Py", "net.supertuxkart.SuperTuxKart", + "net.trowell.typesetter", + "net.veloren.airshipper", "net.waterfox.waterfox", + "net.werwolv.ImHex", + "net.wz2100.wz2100", + "net.xmind.XMind", + "net.zdechov.app.x2048", "network.loki.Session", + "nl.openoffice.bluefish", "one.ablaze.floorp", + "org.alienarena.alienarena", + "org.altlinux.Tuner", "org.azahar_emu.Azahar", "org.bunkus.mkvtoolnix-gui", + "org.cloudcompare.CloudCompare", + "org.cockpit_project.CockpitClient", + "org.contourterminal.Contour", "org.cryptomator.Cryptomator", "org.deskflow.deskflow", + "org.diasurgical.DevilutionX", + "org.dune3d.dune3d", "org.dupot.easyflatpak", "org.equicord.equibop", "org.fcitx.Fcitx5", "org.fedoraproject.MediaWriter", + "org.feichtmeier.Musicpod", + "org.ferdium.Ferdium", "org.fkoehler.KTailctl", "org.flameshot.Flameshot", + "org.flightgear.FlightGear", + "org.fooyin.fooyin", "org.freac.freac", "org.freecad.FreeCAD", "org.freedownloadmanager.Manager", + "org.gabmus.gfeeds", + "org.gabmus.hydrapaper", + "org.gabmus.whatip", + "org.gajim.Gajim", + "org.gaphor.Gaphor", + "org.garudalinux.firedragon", "org.gimp.GIMP", "org.gnome.Boxes", + "org.gnome.Builder", "org.gnome.Calculator", "org.gnome.Calendar", "org.gnome.Characters", + "org.gnome.Chess", "org.gnome.Connections", "org.gnome.Contacts", "org.gnome.Decibels", @@ -214,95 +498,207 @@ "org.gnome.Evolution", "org.gnome.Extensions", "org.gnome.Firmware", + "org.gnome.Fractal", "org.gnome.Logs", "org.gnome.Loupe", "org.gnome.Mahjongg", "org.gnome.Maps", "org.gnome.Mines", + "org.gnome.Music", "org.gnome.Papers", + "org.gnome.Quadrapassel", "org.gnome.Shotwell", "org.gnome.Showtime", "org.gnome.SimpleScan", "org.gnome.Snapshot", + "org.gnome.Sudoku", "org.gnome.TextEditor", + "org.gnome.Totem", "org.gnome.Weather", + "org.gnome.World.Iotas", "org.gnome.World.PikaBackup", "org.gnome.World.Secrets", "org.gnome.baobab", "org.gnome.clocks", + "org.gnome.design.IconLibrary", "org.gnome.font-viewer", + "org.gnome.gedit", + "org.gnome.gitlab.YaLTeR.Identity", "org.gnome.gitlab.YaLTeR.VideoTrimmer", "org.gnome.gitlab.somas.Apostrophe", + "org.gnucash.GnuCash", + "org.gottcode.FocusWriter", + "org.gramps_project.Gramps", + "org.gtkhash.gtkhash", "org.inkscape.Inkscape", + "org.jamovi.jamovi", + "org.jaspstats.JASP", "org.jellyfin.JellyfinDesktop", "org.jellyfin.JellyfinServer", + "org.jousse.vincent.Pomodorolm", + "org.js.nuclear.Nuclear", + "org.kartkrew.RingRacers", + "org.kde.CrowTranslate", + "org.kde.amarok", + "org.kde.arianna", "org.kde.ark", "org.kde.audiotube", + "org.kde.calligra", "org.kde.digikam", "org.kde.dolphin", + "org.kde.dragonplayer", "org.kde.elisa", "org.kde.falkon", "org.kde.filelight", "org.kde.gcompris", + "org.kde.ghostwriter", "org.kde.gwenview", "org.kde.haruna", "org.kde.isoimagewriter", + "org.kde.kaffeine", + "org.kde.kalk", + "org.kde.kalzium", "org.kde.kamoso", + "org.kde.kasts", "org.kde.kate", "org.kde.kcalc", "org.kde.kclock", "org.kde.kdenlive", + "org.kde.kdf", + "org.kde.kget", + "org.kde.kid3", + "org.kde.kleopatra", + "org.kde.kmahjongg", + "org.kde.kmines", + "org.kde.kmymoney", + "org.kde.koko", "org.kde.kolourpaint", "org.kde.konsole", + "org.kde.kontact", "org.kde.kpat", + "org.kde.krdc", + "org.kde.krename", "org.kde.krita", + "org.kde.kshisen", + "org.kde.kstars", + "org.kde.ksudoku", "org.kde.ktorrent", + "org.kde.ktouch", + "org.kde.kwalletmanager5", "org.kde.kweather", + "org.kde.kwrite", + "org.kde.labplot", + "org.kde.marble", + "org.kde.merkuro", "org.kde.minuet", + "org.kde.neochat", "org.kde.okular", + "org.kde.plasmatube", + "org.kde.qrca", + "org.kde.skanlite", + "org.kde.skanpage", + "org.kde.subtitlecomposer", + "org.kde.yakuake", "org.keepassxc.KeePassXC", "org.kicad.KiCad", + "org.kiwix.desktop", "org.learningequality.Kolibri", + "org.leocad.LeoCAD", "org.librecad.librecad", "org.libreoffice.LibreOffice", + "org.librepcb.LibrePCB", "org.libretro.RetroArch", "org.linux_hardware.hw-probe", "org.localsend.localsend_app", "org.luanti.luanti", + "org.meshtastic.meshtasticd", "org.mixxx.Mixxx", + "org.moneymanagerex.MMEX", "org.mozilla.Thunderbird", "org.mozilla.firefox", "org.mozilla.vpn", + "org.nickvision.cavalier", + "org.nickvision.money", + "org.nickvision.tagger", "org.nickvision.tubeconverter", "org.nicotine_plus.Nicotine", + "org.nomacs.ImageLounge", + "org.onionshare.OnionShare", "org.onlyoffice.desktopeditors", + "org.openandroidinstaller.OpenAndroidInstaller", + "org.openmw.OpenMW", "org.openrgb.OpenRGB", + "org.openscad.OpenSCAD", + "org.opensurge2d.OpenSurge", + "org.pencil2d.Pencil2D", + "org.photoqt.PhotoQt", + "org.pitivi.Pitivi", "org.ppsspp.PPSSPP", "org.prismlauncher.PrismLauncher", "org.pulseaudio.pavucontrol", + "org.pvermeer.WebAppHub", "org.qbittorrent.qBittorrent", + "org.qownnotes.QOwnNotes", "org.remmina.Remmina", + "org.rncbc.qpwgraph", + "org.sabnzbd.sabnzbd", "org.scummvm.ScummVM", "org.shotcut.Shotcut", + "org.siril.Siril", + "org.sqlitebrowser.sqlitebrowser", + "org.squidowl.halloy", "org.srb2.SRB2", "org.stellarium.Stellarium", "org.strawberrymusicplayer.strawberry", "org.telegram.desktop", "org.tenacityaudio.Tenacity", + "org.texstudio.TeXstudio", + "org.thonny.Thonny", "org.torproject.torbrowser-launcher", "org.turbowarp.TurboWarp", + "org.tuxpaint.Tuxpaint", "org.upscayl.Upscayl", "org.vinegarhq.Sober", "org.vinegarhq.Vinegar", + "org.virt_manager.virt-manager", + "org.wesnoth.Wesnoth", + "org.wezfurlong.wezterm", "org.x.Warpinator", + "org.xonotic.Xonotic", + "org.zaproxy.ZAP", + "org.zrythm.Zrythm", + "page.codeberg.JakobDev.jdMinecraftLauncher", + "page.codeberg.censor.Censor", + "page.codeberg.impromptux.ytdl-gui", "page.codeberg.libre_menu_editor.LibreMenuEditor", + "page.codeberg.lo_vely.Nucleus", + "page.codeberg.tahoso.azul-box", "page.kramo.Cartridges", "page.tesk.Refine", + "re.fossplant.songrec", + "re.sonny.Eloquent", + "re.sonny.Junction", + "re.sonny.Tangram", + "re.sonny.Workbench", + "rocks.koreader.KOReader", "rocks.shy.VacuumTube", + "rs.ruffle.Ruffle", "ru.linux_gaming.PortProton", "ru.yandex.Browser", + "sa.sy.bluerecorder", + "se.sjoerd.Graphs", + "so.libdb.dissent", + "space.gaiasky.GaiaSky", + "space.rirusha.Cassette", + "studio.planetpeanut.Bobby", "tv.kodi.Kodi", "tv.plex.PlexDesktop", + "tv.plex.PlexHTPC", + "us.materialio.Materialious", + "vn.hoabinh.quan.CoBang", + "website.i2pd.i2pd", + "work.openpaper.Paperwork", + "xyz.hyperplay.HyperPlay", "xyz.ketok.Speedtest", "xyz.z3ntu.razergenie" ]