diff --git a/.github/workflows/test-frontend.yaml b/.github/workflows/test-frontend.yaml
index 7273225f..778319ec 100644
--- a/.github/workflows/test-frontend.yaml
+++ b/.github/workflows/test-frontend.yaml
@@ -94,6 +94,7 @@ jobs:
run: bun run test:e2e
- name: Upload test reports
+ if: always()
uses: actions/upload-artifact@v4
with:
name: reports
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
index 2377e5aa..f3bdfa74 100755
--- a/frontend/.prettierrc
+++ b/frontend/.prettierrc
@@ -2,7 +2,8 @@
"plugins": [
"@ianvs/prettier-plugin-sort-imports",
"./node_modules/prettier-plugin-jsdoc/dist/index.js",
- "prettier-plugin-css-order"
+ "prettier-plugin-css-order",
+ "prettier-plugin-tailwindcss"
],
"importOrder": [
"^react",
diff --git a/frontend/bun.lock b/frontend/bun.lock
index 4e50d73e..285ad68e 100644
--- a/frontend/bun.lock
+++ b/frontend/bun.lock
@@ -3,11 +3,14 @@
"workspaces": {
"": {
"dependencies": {
+ "@fontsource-variable/jetbrains-mono": "^5.2.8",
+ "@fontsource/poppins": "^5.2.7",
"@headlessui/react": "^2.2.4",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7",
"@reactuses/core": "^6.0.5",
+ "@tailwindcss/vite": "^4.1.18",
"@tanstack/react-query": "^5.81.5",
"@tanstack/react-table": "8.21.3",
"clsx": "^2.1.1",
@@ -22,7 +25,6 @@
"cytoscape-klay": "^3.1.4",
"cytoscape-spread": "^3.0.0",
"d3": "^7.9.0",
- "eslint": "^9.30.1",
"gsap": "^3.13.0",
"html-to-image": "^1.11.13",
"javascript-time-ago": "^2.5.11",
@@ -36,6 +38,7 @@
"react-router": "^7.6.3",
"react-time-ago": "^7.3.3",
"seedrandom": "^3.0.5",
+ "tailwindcss": "^4.1.18",
"ua-parser-js": "^2.0.4",
},
"devDependencies": {
@@ -70,6 +73,7 @@
"prettier": "^3.6.2",
"prettier-plugin-css-order": "^2.1.2",
"prettier-plugin-jsdoc": "^1.3.3",
+ "prettier-plugin-tailwindcss": "^0.7.2",
"type-fest": "^4.41.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.1",
@@ -207,6 +211,10 @@
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
+ "@fontsource-variable/jetbrains-mono": ["@fontsource-variable/jetbrains-mono@5.2.8", "", {}, "sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q=="],
+
+ "@fontsource/poppins": ["@fontsource/poppins@5.2.7", "", {}, "sha512-6uQyPmseo4FgI97WIhA4yWRlNaoLk4vSDK/PyRwdqqZb5zAEuc+Kunt8JTMcsHYUEGYBtN15SNkMajMdqUSUmg=="],
+
"@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.4", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.1", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA=="],
"@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="],
@@ -247,6 +255,8 @@
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="],
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],
@@ -697,6 +707,36 @@
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
+ "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
+
+ "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
+
+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
+
+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
+
+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
+
+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
+
+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
+
+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
+
+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
+
+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
+
+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
+
+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
+
+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
+
+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
+
+ "@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
+
"@tanstack/query-core": ["@tanstack/query-core@5.81.5", "", {}, "sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q=="],
"@tanstack/react-query": ["@tanstack/react-query@5.81.5", "", { "dependencies": { "@tanstack/query-core": "5.81.5" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw=="],
@@ -1119,6 +1159,8 @@
"detect-europe-js": ["detect-europe-js@0.1.2", "", {}, "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow=="],
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
@@ -1139,6 +1181,8 @@
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
+ "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
+
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@@ -1389,6 +1433,8 @@
"javascript-time-ago": ["javascript-time-ago@2.5.11", "", { "dependencies": { "relative-time-format": "^1.1.6" } }, "sha512-Zeyf5R7oM1fSMW9zsU3YgAYwE0bimEeF54Udn2ixGd8PUwu+z1Yc5t4Y8YScJDMHD6uCx6giLt3VJR5K4CMwbg=="],
+ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
"jotai": ["jotai@2.12.5", "", { "peerDependencies": { "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-G8m32HW3lSmcz/4mbqx0hgJIQ0ekndKWiYP7kWVKi0p6saLXdSoye+FZiOFyonnd7Q482LCzm8sMDl7Ar1NWDw=="],
"jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
@@ -1437,6 +1483,30 @@
"lighthouse-stack-packs": ["lighthouse-stack-packs@1.12.2", "", {}, "sha512-Ug8feS/A+92TMTCK6yHYLwaFMuelK/hAKRMdldYkMNwv+d9PtWxjXEg6rwKtsUXTADajhdrhXyuNCJ5/sfmPFw=="],
+ "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
+
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
+
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
+
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
+
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
+
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
+
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
+
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
+
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
+
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
+
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
+
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
+
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
@@ -1457,6 +1527,8 @@
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+
"marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
@@ -1635,6 +1707,8 @@
"prettier-plugin-jsdoc": ["prettier-plugin-jsdoc@1.3.3", "", { "dependencies": { "binary-searching": "^2.0.5", "comment-parser": "^1.4.0", "mdast-util-from-markdown": "^2.0.0" }, "peerDependencies": { "prettier": "^3.0.0" } }, "sha512-YIxejcbPYK4N58jHGiXjYvrCzBMyvV2AEMSoF5LvqqeMEI0nsmww57I6NGnpVc0AU9ncFCTEBoYHN/xuBf80YA=="],
+ "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.2", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA=="],
+
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
@@ -1805,6 +1879,10 @@
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
+ "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
+
+ "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
+
"tar-fs": ["tar-fs@3.1.0", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w=="],
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
@@ -1949,6 +2027,18 @@
"@sentry/node/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
"@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
@@ -1973,6 +2063,8 @@
"lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+ "magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
diff --git a/frontend/index.html b/frontend/index.html
index b49128bd..1c4cd261 100755
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -19,15 +19,6 @@
-
-
-
-
-
diff --git a/frontend/package.json b/frontend/package.json
index 9a5d1505..b0473410 100755
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -15,11 +15,14 @@
"clean": "rm -rf node_modules dist bun.lock && bun pm cache rm"
},
"dependencies": {
+ "@fontsource-variable/jetbrains-mono": "^5.2.8",
+ "@fontsource/poppins": "^5.2.7",
"@headlessui/react": "^2.2.4",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7",
"@reactuses/core": "^6.0.5",
+ "@tailwindcss/vite": "^4.1.18",
"@tanstack/react-query": "^5.81.5",
"@tanstack/react-table": "8.21.3",
"clsx": "^2.1.1",
@@ -47,6 +50,7 @@
"react-router": "^7.6.3",
"react-time-ago": "^7.3.3",
"seedrandom": "^3.0.5",
+ "tailwindcss": "^4.1.18",
"ua-parser-js": "^2.0.4"
},
"devDependencies": {
@@ -81,6 +85,7 @@
"prettier": "^3.6.2",
"prettier-plugin-css-order": "^2.1.2",
"prettier-plugin-jsdoc": "^1.3.3",
+ "prettier-plugin-tailwindcss": "^0.7.2",
"type-fest": "^4.41.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.1",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 4f511964..05b8b9fe 100755
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,8 +1,8 @@
-import "@/global/theme.css";
-import "@/global/styles.css";
-import "@/global/text.css";
-import "@/global/layout.css";
-import "@/global/effects.css";
+import "@/styles.css";
+import "@fontsource/poppins/300.css";
+import "@fontsource/poppins/500.css";
+import "@fontsource/poppins/600.css";
+import "@fontsource-variable/jetbrains-mono";
import { IconContext } from "react-icons";
import {
createBrowserRouter,
diff --git a/frontend/src/components/Alert.module.css b/frontend/src/components/Alert.module.css
deleted file mode 100755
index 2efe61a5..00000000
--- a/frontend/src/components/Alert.module.css
+++ /dev/null
@@ -1,24 +0,0 @@
-.alert {
- z-index: 0;
- position: relative;
- flex-shrink: 0;
- padding: 20px;
- overflow: hidden;
- gap: 20px !important;
- border-radius: var(--rounded);
- line-height: var(--spacing);
-}
-
-.alert::before {
- z-index: -1;
- position: absolute;
- inset: 0;
- background: var(--color);
- content: "";
- opacity: 0.1;
-}
-
-.alert > svg {
- height: 20px;
- color: var(--color);
-}
diff --git a/frontend/src/components/Alert.tsx b/frontend/src/components/Alert.tsx
index 64a05b89..d8bdf9f1 100755
--- a/frontend/src/components/Alert.tsx
+++ b/frontend/src/components/Alert.tsx
@@ -1,11 +1,21 @@
import type { ComponentProps } from "react";
import clsx from "clsx";
import Mark from "@/components/Mark";
-import classes from "./Alert.module.css";
/** static box of certain type with icon and text contents */
-const Alert = ({ className, ...props }: ComponentProps) => {
- return ;
+const Alert = ({
+ className,
+ children,
+ ...props
+}: ComponentProps) => {
+ return (
+
+ {children}
+
+ );
};
export default Alert;
diff --git a/frontend/src/components/AnalysisCard.module.css b/frontend/src/components/AnalysisCard.module.css
deleted file mode 100755
index 6b19d8df..00000000
--- a/frontend/src/components/AnalysisCard.module.css
+++ /dev/null
@@ -1,23 +0,0 @@
-.card {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- padding: 20px;
- gap: 10px;
- border: solid 2px transparent;
- text-decoration: none;
- transition: border-color var(--fast);
-}
-
-.card:hover {
- border-color: var(--accent);
-}
-
-.card:has(button:hover) {
- border-color: transparent;
-}
-
-.button {
- align-self: center;
- margin-top: 10px;
-}
diff --git a/frontend/src/components/AnalysisCard.tsx b/frontend/src/components/AnalysisCard.tsx
index 8de81240..d51e8f78 100755
--- a/frontend/src/components/AnalysisCard.tsx
+++ b/frontend/src/components/AnalysisCard.tsx
@@ -1,9 +1,8 @@
-import clsx from "clsx";
import type { Analysis } from "@/api/types";
import Ago from "@/components/Ago";
import Link from "@/components/Link";
-import Mark, { type Type } from "@/components/Mark";
-import classes from "./AnalysisCard.module.css";
+import Mark from "@/components/Mark";
+import type { Type } from "@/components/Mark";
type Props = {
analysis: Analysis;
@@ -23,13 +22,13 @@ const AnalysisCard = ({
return (
- {name}
- {type}
- {info && {info}
}
- {started && }
+ {name}
+ {type}
+ {info && {info}
}
+ {started && }
{status && {status.info}}
);
diff --git a/frontend/src/components/Asterisk.module.css b/frontend/src/components/Asterisk.module.css
deleted file mode 100755
index 036a4d8a..00000000
--- a/frontend/src/components/Asterisk.module.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.asterisk {
- margin: 0 -5px;
- translate: 0 -0.35em;
- scale: 0.75;
- color: var(--error);
-}
diff --git a/frontend/src/components/Asterisk.tsx b/frontend/src/components/Asterisk.tsx
index aa3b2514..f4c960c8 100755
--- a/frontend/src/components/Asterisk.tsx
+++ b/frontend/src/components/Asterisk.tsx
@@ -1,13 +1,12 @@
import { FaAsterisk } from "react-icons/fa6";
import Tooltip from "@/components/Tooltip";
-import classes from "./Asterisk.module.css";
/** asterisk for things like required form fields */
const Asterisk = () => (
{/* https://github.com/react-icons/react-icons/issues/336 */}
-
+
);
diff --git a/frontend/src/components/Badge.module.css b/frontend/src/components/Badge.module.css
deleted file mode 100755
index 4ce75e1e..00000000
--- a/frontend/src/components/Badge.module.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.badge {
- display: inline-grid;
- flex-shrink: 0;
- place-content: center;
- width: 1.75em;
- height: 1.75em;
- border-radius: 999px;
- background: var(--pale);
- color: var(--deep);
- font-weight: var(--bold);
- font-size: 1rem;
-}
diff --git a/frontend/src/components/Badge.tsx b/frontend/src/components/Badge.tsx
index f05f2b59..1be814fb 100755
--- a/frontend/src/components/Badge.tsx
+++ b/frontend/src/components/Badge.tsx
@@ -1,6 +1,5 @@
import type { ReactNode } from "react";
import clsx from "clsx";
-import classes from "./Badge.module.css";
type Props = {
/** class on badge */
@@ -14,7 +13,13 @@ type Props = {
* directly.
*/
const Badge = ({ className, children }: Props) => (
-
+
{children}
);
diff --git a/frontend/src/components/Button.module.css b/frontend/src/components/Button.module.css
deleted file mode 100755
index df087cfd..00000000
--- a/frontend/src/components/Button.module.css
+++ /dev/null
@@ -1,57 +0,0 @@
-.button {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-height: 40px;
- padding: 5px 20px;
- gap: 10px;
- border-radius: var(--rounded);
- text-decoration: none;
- transition:
- background var(--fast),
- color var(--fast);
-}
-
-.square {
- min-width: 40px;
- min-height: 40px;
- padding: 0;
- border-radius: 999px;
-}
-
-.hollow {
- padding: 5px;
- color: var(--accent);
-}
-
-.hollow:hover {
- color: var(--deep);
-}
-
-.normal {
- background: var(--accent);
- color: var(--white);
-}
-
-.normal:hover {
- background: var(--deep);
- color: var(--white);
-}
-
-.normal:focus-visible {
- outline-color: var(--accent);
-}
-
-.critical {
- background: var(--black);
- color: var(--white);
-}
-
-.critical:hover {
- background: var(--deep);
- color: var(--white);
-}
-
-.critical:focus-visible {
- outline-color: var(--black);
-}
diff --git a/frontend/src/components/Button.tsx b/frontend/src/components/Button.tsx
index 962c312f..ea73c0fb 100755
--- a/frontend/src/components/Button.tsx
+++ b/frontend/src/components/Button.tsx
@@ -3,7 +3,6 @@ import clsx from "clsx";
import { useForm } from "@/components/Form";
import Link from "@/components/Link";
import Tooltip from "@/components/Tooltip";
-import classes from "./Button.module.css";
type Props = Base & Description & (_Link | _Button);
@@ -14,6 +13,8 @@ type Base = {
flip?: boolean;
/** look */
design?: "normal" | "hollow" | "critical";
+ /** sizing */
+ size?: "compact" | "normal";
/** class on button */
className?: string;
};
@@ -49,6 +50,7 @@ const Button = ({
icon,
flip = false,
design = "normal",
+ size = "normal",
className,
tooltip,
...props
@@ -67,9 +69,15 @@ const Button = ({
);
/** class name string */
- const _class = clsx(className, classes.button, classes[design], {
- [classes.square!]: !text && !!icon,
- });
+ const _class = clsx(
+ "gap-2",
+ size === "compact" ? "p-1" : "p-3",
+ !!icon && !text ? "rounded-full" : "rounded",
+ design === "hollow" && "text-accent hover:text-deep",
+ design === "normal" && "bg-accent text-white hover:bg-deep outline-accent",
+ design === "critical" && "bg-black text-white hover:bg-deep outline-black",
+ className,
+ );
/** link to parent form component */
const form = useForm();
diff --git a/frontend/src/components/Chart.module.css b/frontend/src/components/Chart.module.css
deleted file mode 100644
index d62aa6b8..00000000
--- a/frontend/src/components/Chart.module.css
+++ /dev/null
@@ -1,55 +0,0 @@
-.container {
- display: grid;
- position: relative;
- place-items: center;
- max-width: 100%;
- padding: 20px;
- overflow: auto;
- resize: both;
-}
-
-.svg {
- overflow: visible;
-}
-
-.debug {
- fill: none;
- stroke: red;
-}
-
-.reset-anchor {
- display: none;
- position: sticky;
- right: 5px;
- bottom: 5px;
- place-self: flex-end;
- width: 0;
- height: 0;
-}
-
-.reset {
- width: 20px;
- height: 20px;
-}
-
-.container[style*="width"] .reset-anchor,
-.container[style*="height"] .reset-anchor {
- display: flex;
-}
-
-.printing {
- aspect-ratio: unset;
- width: unset;
- min-width: unset;
- max-width: unset;
- height: 100vh;
- min-height: unset;
- max-height: unset;
- padding: 0;
- overflow: visible;
- border: none;
- border-radius: 0;
- background: var(--white);
- box-shadow: none;
- resize: unset;
-}
diff --git a/frontend/src/components/Chart.tsx b/frontend/src/components/Chart.tsx
index f4221222..d3f3c3b7 100644
--- a/frontend/src/components/Chart.tsx
+++ b/frontend/src/components/Chart.tsx
@@ -7,13 +7,11 @@ import { clamp } from "lodash";
import { useDebounce, useElementSize, useFullscreen } from "@reactuses/core";
import Button from "@/components/Button";
import Download from "@/components/Download";
-import Flex from "@/components/Flex";
import Tooltip from "@/components/Tooltip";
import { isSafari } from "@/util/browser";
import { getViewBoxFit } from "@/util/dom";
import type { Filename, Tabular } from "@/util/download";
import { usePrint, useTextSize, useTheme } from "@/util/hooks";
-import classes from "./Chart.module.css";
type Props = {
/** title text */
@@ -59,7 +57,7 @@ const Chart = ({
children,
onClick,
containerProps: { className: containerClassName, ...containerProps } = {},
- svgProps: { className: svgClassName, ...svgProps } = {},
+ svgProps = {},
}: Props) => {
const containerRef = useRef(null);
const svgRef = useRef(null);
@@ -154,10 +152,11 @@ const Chart = ({
.reset-handle]:grid",
+ printing
+ ? "aspect-auto h-screen max-h-none min-h-0 w-auto max-w-none min-w-0 resize-none overflow-visible rounded-none border-0 bg-white p-0 shadow-none"
+ : "rounded bg-white shadow",
containerClassName,
- printing && classes.printing,
)}
// https://github.com/dequelabs/axe-core/issues/4566
// eslint-disable-next-line
@@ -165,12 +164,7 @@ const Chart = ({
onClick={onClick}
{...containerProps}
>
-