diff --git a/opus_submitter/package.json b/opus_submitter/package.json index b2ca933..f127c2a 100644 --- a/opus_submitter/package.json +++ b/opus_submitter/package.json @@ -3,18 +3,25 @@ "private": true, "version": "0.0.0", "type": "module", + "packageManager": "pnpm@9.0.0", "scripts": { "dev": "vite", "build": "vue-tsc -b && vite build", "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.1.16", + "install": "^0.13.0", + "tailwindcss": "^4.1.16", + "tesseract.js": "^5.1.1", "vue": "^3.5.22" }, "devDependencies": { + "@mdi/font": "^7.4.47", "@types/node": "^24.6.0", "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", + "daisyui": "^5.3.10", "typescript": "~5.9.3", "vite": "^7.1.7", "vue-tsc": "^3.1.0" diff --git a/opus_submitter/pnpm-lock.yaml b/opus_submitter/pnpm-lock.yaml index 4c0b1bf..caddada 100644 --- a/opus_submitter/pnpm-lock.yaml +++ b/opus_submitter/pnpm-lock.yaml @@ -8,25 +8,43 @@ importers: .: dependencies: + '@tailwindcss/vite': + specifier: ^4.1.16 + version: 4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2)) + install: + specifier: ^0.13.0 + version: 0.13.0 + tailwindcss: + specifier: ^4.1.16 + version: 4.1.16 + tesseract.js: + specifier: ^5.1.1 + version: 5.1.1 vue: specifier: ^3.5.22 version: 3.5.22(typescript@5.9.3) devDependencies: + '@mdi/font': + specifier: ^7.4.47 + version: 7.4.47 '@types/node': specifier: ^24.6.0 version: 24.9.2 '@vitejs/plugin-vue': specifier: ^6.0.1 - version: 6.0.1(vite@7.1.12(@types/node@24.9.2))(vue@3.5.22(typescript@5.9.3)) + version: 6.0.1(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.22(typescript@5.9.3)) '@vue/tsconfig': specifier: ^0.8.1 version: 0.8.1(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)) + daisyui: + specifier: ^5.3.10 + version: 5.3.10 typescript: specifier: ~5.9.3 version: 5.9.3 vite: specifier: ^7.1.7 - version: 7.1.12(@types/node@24.9.2) + version: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2) vue-tsc: specifier: ^3.1.0 version: 3.1.2(typescript@5.9.3) @@ -206,9 +224,25 @@ packages: cpu: [x64] os: [win32] + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mdi/font@7.4.47': + resolution: {integrity: sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==} + '@rolldown/pluginutils@1.0.0-beta.29': resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} @@ -322,6 +356,96 @@ packages: cpu: [x64] os: [win32] + '@tailwindcss/node@4.1.16': + resolution: {integrity: sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==} + + '@tailwindcss/oxide-android-arm64@4.1.16': + resolution: {integrity: sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.16': + resolution: {integrity: sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.16': + resolution: {integrity: sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.16': + resolution: {integrity: sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16': + resolution: {integrity: sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.16': + resolution: {integrity: sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.16': + resolution: {integrity: sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.16': + resolution: {integrity: sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.16': + resolution: {integrity: sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.16': + resolution: {integrity: sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.16': + resolution: {integrity: sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.16': + resolution: {integrity: sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.16': + resolution: {integrity: sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.16': + resolution: {integrity: sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -395,9 +519,23 @@ packages: alien-signals@3.0.3: resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==} + bmp-js@0.1.0: + resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + daisyui@5.3.10: + resolution: {integrity: sha512-vmjyPmm0hvFhA95KB6uiGmWakziB2pBv6CUcs5Ka/3iMBMn9S+C3SZYx9G9l2JrgTZ1EFn61F/HrPcwaUm2kLQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -424,6 +562,96 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + idb-keyval@6.2.2: + resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} + + install@0.13.0: + resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} + engines: {node: '>= 0.10'} + + is-electron@2.2.2: + resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} + + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -435,6 +663,19 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + opencollective-postinstall@2.0.3: + resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} + hasBin: true + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -449,6 +690,9 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + rollup@4.52.5: resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -458,10 +702,26 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + tailwindcss@4.1.16: + resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tesseract.js-core@5.1.1: + resolution: {integrity: sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ==} + + tesseract.js@5.1.1: + resolution: {integrity: sha512-lzVl/Ar3P3zhpUT31NjqeCo1f+D5+YfpZ5J62eo2S14QNVOmHBTtbchHm/YAbOOOzCegFnKf4B3Qih9LuldcYQ==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -527,6 +787,18 @@ packages: typescript: optional: true + wasm-feature-detect@1.8.0: + resolution: {integrity: sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + zlibjs@0.3.1: + resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==} + snapshots: '@babel/helper-string-parser@7.27.1': {} @@ -620,8 +892,27 @@ snapshots: '@esbuild/win32-x64@0.25.11': optional: true + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mdi/font@7.4.47': {} + '@rolldown/pluginutils@1.0.0-beta.29': {} '@rollup/rollup-android-arm-eabi@4.52.5': @@ -690,16 +981,84 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true + '@tailwindcss/node@4.1.16': + dependencies: + '@jridgewell/remapping': 2.3.5 + 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.16 + + '@tailwindcss/oxide-android-arm64@4.1.16': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.16': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.16': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.16': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.16': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.16': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.16': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.16': + optional: true + + '@tailwindcss/oxide@4.1.16': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.16 + '@tailwindcss/oxide-darwin-arm64': 4.1.16 + '@tailwindcss/oxide-darwin-x64': 4.1.16 + '@tailwindcss/oxide-freebsd-x64': 4.1.16 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.16 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.16 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.16 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.16 + '@tailwindcss/oxide-linux-x64-musl': 4.1.16 + '@tailwindcss/oxide-wasm32-wasi': 4.1.16 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.16 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.16 + + '@tailwindcss/vite@4.1.16(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@tailwindcss/node': 4.1.16 + '@tailwindcss/oxide': 4.1.16 + tailwindcss: 4.1.16 + vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2) + '@types/estree@1.0.8': {} '@types/node@24.9.2': dependencies: undici-types: 7.16.0 - '@vitejs/plugin-vue@6.0.1(vite@7.1.12(@types/node@24.9.2))(vue@3.5.22(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.1(vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.22(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.12(@types/node@24.9.2) + vite: 7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2) vue: 3.5.22(typescript@5.9.3) '@volar/language-core@2.4.23': @@ -787,8 +1146,19 @@ snapshots: alien-signals@3.0.3: {} + bmp-js@0.1.0: {} + csstype@3.1.3: {} + daisyui@5.3.10: {} + + detect-libc@2.1.2: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + entities@4.5.0: {} esbuild@0.25.11: @@ -829,6 +1199,67 @@ snapshots: fsevents@2.3.3: optional: true + graceful-fs@4.2.11: {} + + idb-keyval@6.2.2: {} + + install@0.13.0: {} + + is-electron@2.2.2: {} + + is-url@1.2.4: {} + + jiti@2.6.1: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + 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 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -837,6 +1268,12 @@ snapshots: nanoid@3.3.11: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + opencollective-postinstall@2.0.3: {} + path-browserify@1.0.1: {} picocolors@1.1.1: {} @@ -849,6 +1286,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + regenerator-runtime@0.13.11: {} + rollup@4.52.5: dependencies: '@types/estree': 1.0.8 @@ -879,16 +1318,39 @@ snapshots: source-map-js@1.2.1: {} + tailwindcss@4.1.16: {} + + tapable@2.3.0: {} + + tesseract.js-core@5.1.1: {} + + tesseract.js@5.1.1: + dependencies: + bmp-js: 0.1.0 + idb-keyval: 6.2.2 + is-electron: 2.2.2 + is-url: 1.2.4 + node-fetch: 2.7.0 + opencollective-postinstall: 2.0.3 + regenerator-runtime: 0.13.11 + tesseract.js-core: 5.1.1 + wasm-feature-detect: 1.8.0 + zlibjs: 0.3.1 + transitivePeerDependencies: + - encoding + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tr46@0.0.3: {} + typescript@5.9.3: {} undici-types@7.16.0: {} - vite@7.1.12(@types/node@24.9.2): + vite@7.1.12(@types/node@24.9.2)(jiti@2.6.1)(lightningcss@1.30.2): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -899,6 +1361,8 @@ snapshots: optionalDependencies: '@types/node': 24.9.2 fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 vscode-uri@3.1.0: {} @@ -917,3 +1381,14 @@ snapshots: '@vue/shared': 3.5.22 optionalDependencies: typescript: 5.9.3 + + wasm-feature-detect@1.8.0: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + zlibjs@0.3.1: {} diff --git a/opus_submitter/src/App.vue b/opus_submitter/src/App.vue index 638ed0a..00e56d4 100644 --- a/opus_submitter/src/App.vue +++ b/opus_submitter/src/App.vue @@ -1,6 +1,261 @@ diff --git a/opus_submitter/src/components/FileUpload.vue b/opus_submitter/src/components/FileUpload.vue new file mode 100644 index 0000000..3c9c15a --- /dev/null +++ b/opus_submitter/src/components/FileUpload.vue @@ -0,0 +1,300 @@ + + + diff --git a/opus_submitter/src/components/PuzzleCard.vue b/opus_submitter/src/components/PuzzleCard.vue new file mode 100644 index 0000000..d1149c0 --- /dev/null +++ b/opus_submitter/src/components/PuzzleCard.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/opus_submitter/src/components/SubmissionForm.vue b/opus_submitter/src/components/SubmissionForm.vue new file mode 100644 index 0000000..0d2c3d5 --- /dev/null +++ b/opus_submitter/src/components/SubmissionForm.vue @@ -0,0 +1,147 @@ + + + diff --git a/opus_submitter/src/main.ts b/opus_submitter/src/main.ts index c8772ec..8233cba 100644 --- a/opus_submitter/src/main.ts +++ b/opus_submitter/src/main.ts @@ -1,4 +1,5 @@ import { createApp } from 'vue' import App from '@/App.vue' +import './style.css' createApp(App).mount('#app') diff --git a/opus_submitter/src/services/ocrService.ts b/opus_submitter/src/services/ocrService.ts new file mode 100644 index 0000000..53d8cef --- /dev/null +++ b/opus_submitter/src/services/ocrService.ts @@ -0,0 +1,259 @@ +import { createWorker } from 'tesseract.js'; + +export interface OpusMagnumData { + puzzle: string; + cost: string; + cycles: string; + area: string; +} + +export interface OCRRegion { + x: number; + y: number; + width: number; + height: number; +} + +export class OpusMagnumOCRService { + private worker: Tesseract.Worker | null = null; + + // Regions based on main.py coordinates (adjusted for web usage) + private readonly regions: Record = { + puzzle: { x: 15, y: 600, width: 330, height: 28 }, + cost: { x: 412, y: 603, width: 65, height: 22 }, + cycles: { x: 577, y: 603, width: 65, height: 22 }, + area: { x: 739, y: 603, width: 65, height: 22 } + }; + + async initialize(): Promise { + if (this.worker) return; + + this.worker = await createWorker('eng'); + await this.worker.setParameters({ + tessedit_ocr_engine_mode: '3', + tessedit_pageseg_mode: '7' + }); + } + + async extractOpusMagnumData(imageFile: File): Promise { + if (!this.worker) { + await this.initialize(); + } + + // Convert file to image element for canvas processing + const imageUrl = URL.createObjectURL(imageFile); + const img = new Image(); + + return new Promise((resolve, reject) => { + img.onload = async () => { + try { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d')!; + + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + + // Extract text from each region + const results: Partial = {}; + + for (const [key, region] of Object.entries(this.regions)) { + const regionCanvas = document.createElement('canvas'); + const regionCtx = regionCanvas.getContext('2d')!; + + regionCanvas.width = region.width; + regionCanvas.height = region.height; + + // Extract region from main image + regionCtx.drawImage( + canvas, + region.x, region.y, region.width, region.height, + 0, 0, region.width, region.height + ); + + // Convert to grayscale and invert (similar to main.py processing) + const imageData = regionCtx.getImageData(0, 0, region.width, region.height); + this.preprocessImage(imageData); + regionCtx.putImageData(imageData, 0, 0); + + // Configure OCR based on content type + let config: any = {}; + if (key === 'cost') { + // Cost field has digits + 'G' for gold (content type: 'digits_with_6') + await this.worker!.setParameters({ + tessedit_char_whitelist: '0123456789G' + }); + } else if (key === 'cycles' || key === 'area') { + // Pure digits (content type: 'digits') + await this.worker!.setParameters({ + tessedit_char_whitelist: '0123456789' + }); + } else if (key === 'puzzle') { + // Puzzle name - allow alphanumeric, spaces, and dashes + await this.worker!.setParameters({ + tessedit_char_whitelist: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 -' + }); + } else { + // Default - allow all characters + await this.worker!.setParameters({ + tessedit_char_whitelist: '' + }); + } + + // Perform OCR on the region + const { data: { text } } = await this.worker!.recognize(regionCanvas); + let cleanText = text.trim(); + + // Post-process based on field type + if (key === 'cost') { + // Handle common OCR misreadings where G is read as 6 + // If the text ends with 6 and looks like it should be G, remove it + if (cleanText.endsWith('6') && cleanText.length > 1) { + // Check if removing the last character gives a reasonable cost value + const withoutLast = cleanText.slice(0, -1); + if (/^\d+$/.test(withoutLast)) { + cleanText = withoutLast; + } + } + // Remove any trailing G characters + cleanText = cleanText.replace(/G+$/g, ''); + // Ensure only digits remain + cleanText = cleanText.replace(/[^0-9]/g, ''); + } else if (key === 'cycles' || key === 'area') { + // Ensure only digits remain + cleanText = cleanText.replace(/[^0-9]/g, ''); + } else if (key === 'puzzle') { + // Post-process puzzle names + cleanText = this.processPuzzleName(cleanText); + } + + results[key as keyof OpusMagnumData] = cleanText; + } + + URL.revokeObjectURL(imageUrl); + + resolve({ + puzzle: results.puzzle || '', + cost: results.cost || '', + cycles: results.cycles || '', + area: results.area || '' + }); + } catch (error) { + URL.revokeObjectURL(imageUrl); + reject(error); + } + }; + + img.onerror = () => { + URL.revokeObjectURL(imageUrl); + reject(new Error('Failed to load image')); + }; + + img.src = imageUrl; + }); + } + + private preprocessImage(imageData: ImageData): void { + // Convert to grayscale and invert (similar to cv2.bitwise_not in main.py) + const data = imageData.data; + + for (let i = 0; i < data.length; i += 4) { + // Convert to grayscale + const gray = Math.round(0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]); + + // Invert the grayscale value + const inverted = 255 - gray; + + data[i] = inverted; // Red + data[i + 1] = inverted; // Green + data[i + 2] = inverted; // Blue + // Alpha channel (data[i + 3]) remains unchanged + } + } + + private processPuzzleName(rawText: string): string { + let processed = rawText.trim(); + + // If no dash is present but we have digits, try to insert one + if (!processed.includes('-') && /\d/.test(processed)) { + // Common pattern: "P4141" should become "P41-41" + // Look for patterns like P[digits][digits] where the last part might be a separate number + const match = processed.match(/^([A-Z]+\d+)(\d{1,3})$/); + if (match) { + processed = `${match[1]}-${match[2]}`; + } + // Handle cases like "4141" -> "41-41" (missing P prefix) + else if (/^\d{3,4}$/.test(processed)) { + const mid = Math.floor(processed.length / 2); + processed = `P${processed.slice(0, mid)}-${processed.slice(mid)}`; + } + } + + // Clean up spacing around dashes + processed = processed.replace(/\s*-\s*/g, '-'); + + // Ensure proper spacing + processed = processed.replace(/([A-Z])(\d)/g, '$1$2'); + processed = processed.replace(/(\d)([A-Z])/g, '$1 $2'); + + // Add P prefix if missing and starts with digits + if (/^\d/.test(processed) && !processed.startsWith('P')) { + processed = 'P' + processed; + } + + return processed; + } + + async terminate(): Promise { + if (this.worker) { + await this.worker.terminate(); + this.worker = null; + } + } + + // Utility method to validate if an image looks like an Opus Magnum screenshot + static isValidOpusMagnumImage(file: File): boolean { + // Basic validation - could be enhanced with actual image analysis + const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']; + return validTypes.includes(file.type); + } + + // Debug method to visualize OCR regions (similar to main.py debug rectangles) + static drawDebugRegions(imageFile: File): Promise { + return new Promise((resolve, reject) => { + const imageUrl = URL.createObjectURL(imageFile); + const img = new Image(); + + img.onload = () => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d')!; + + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + + // Draw debug rectangles + ctx.strokeStyle = '#00ff00'; + ctx.lineWidth = 2; + + const service = new OpusMagnumOCRService(); + Object.values(service.regions).forEach(region => { + ctx.strokeRect(region.x, region.y, region.width, region.height); + }); + + URL.revokeObjectURL(imageUrl); + resolve(canvas.toDataURL()); + }; + + img.onerror = () => { + URL.revokeObjectURL(imageUrl); + reject(new Error('Failed to load image for debug')); + }; + + img.src = imageUrl; + }); + } +} + +// Singleton instance for the application +export const ocrService = new OpusMagnumOCRService(); diff --git a/opus_submitter/src/style.css b/opus_submitter/src/style.css new file mode 100644 index 0000000..bbb1b08 --- /dev/null +++ b/opus_submitter/src/style.css @@ -0,0 +1,39 @@ +@import '@mdi/font/css/materialdesignicons.css'; +@import "tailwindcss"; + +@plugin "daisyui"; +@plugin "daisyui/theme" { + name: "dim"; + default: false; + prefersdark: false; + color-scheme: "dark"; + --color-base-100: oklch(30.857% 0.023 264.149); + --color-base-200: oklch(28.036% 0.019 264.182); + --color-base-300: oklch(26.346% 0.018 262.177); + --color-base-content: oklch(82.901% 0.031 222.959); + --color-primary: oklch(86.133% 0.141 139.549); + --color-primary-content: oklch(17.226% 0.028 139.549); + --color-secondary: oklch(73.375% 0.165 35.353); + --color-secondary-content: oklch(14.675% 0.033 35.353); + --color-accent: oklch(74.229% 0.133 311.379); + --color-accent-content: oklch(14.845% 0.026 311.379); + --color-neutral: oklch(24.731% 0.02 264.094); + --color-neutral-content: oklch(82.901% 0.031 222.959); + --color-info: oklch(86.078% 0.142 206.182); + --color-info-content: oklch(17.215% 0.028 206.182); + --color-success: oklch(86.171% 0.142 166.534); + --color-success-content: oklch(17.234% 0.028 166.534); + --color-warning: oklch(86.163% 0.142 94.818); + --color-warning-content: oklch(17.232% 0.028 94.818); + --color-error: oklch(82.418% 0.099 33.756); + --color-error-content: oklch(16.483% 0.019 33.756); + --radius-selector: 2rem; + --radius-field: 0.25rem; + --radius-box: 0.25rem; + --size-selector: 0.25rem; + --size-field: 0.25rem; + --border: 1px; + --depth: 0; + --noise: 0; +} + diff --git a/opus_submitter/src/types/index.ts b/opus_submitter/src/types/index.ts new file mode 100644 index 0000000..a6c81ca --- /dev/null +++ b/opus_submitter/src/types/index.ts @@ -0,0 +1,59 @@ +export interface SteamCollection { + id: number + steam_id: string + title: string + description: string + author_name: string + total_items: number + unique_visitors: number + current_favorites: number + created_at: string + updated_at: string +} + +export interface SteamCollectionItem { + id: number + steam_item_id: string + title: string + author_name: string + description: string + tags: string[] + order_index: number + collection: number + created_at: string + updated_at: string +} + +export interface OpusMagnumData { + puzzle: string + cost: string + cycles: string + area: string +} + +export interface SubmissionFile { + file: File + preview: string + type: 'image' | 'gif' + ocrData?: OpusMagnumData + ocrProcessing?: boolean + ocrError?: string +} + +export interface PuzzleResponse { + id?: number + puzzle_id: number + puzzle_name: string + cost?: string + cycles?: string + area?: string + files: SubmissionFile[] +} + +export interface Submission { + id?: number + responses: PuzzleResponse[] + notes?: string + created_at?: string + updated_at?: string +} diff --git a/opus_submitter/templates/index.html b/opus_submitter/templates/index.html index 7a9f755..6729626 100644 --- a/opus_submitter/templates/index.html +++ b/opus_submitter/templates/index.html @@ -1,11 +1,11 @@ {% load django_vite %} - + - opus_submitter + Opus Magnum Puzzle Submitter {% vite_hmr_client %} {% vite_asset 'src/main.ts' %} diff --git a/opus_submitter/vite.config.ts b/opus_submitter/vite.config.ts index b2da482..3350ab5 100644 --- a/opus_submitter/vite.config.ts +++ b/opus_submitter/vite.config.ts @@ -2,11 +2,12 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' import { fileURLToPath } from 'node:url' +import tailwindcss from '@tailwindcss/vite' // https://vitejs.dev/config/ export default defineConfig({ base: '/static/', - plugins: [vue()], + plugins: [vue(), tailwindcss()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)),