diff --git a/.gitattributes b/.gitattributes index 0a0c0bd..01c3dfe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,6 @@ *.gif filter=lfs diff=lfs merge=lfs -text *.webp filter=lfs diff=lfs merge=lfs -text *.svg filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/README.md b/README.md index b3578f1..040a745 100644 --- a/README.md +++ b/README.md @@ -90,3 +90,9 @@ Focused on building a simple user experience and intuitive UI as a curated commu - [elysia](https://elysiajs.com/) for the APIs - [webview](https://github.com/webview/webview) for launching existing system webviews instead of full browser if possible. - [emulatorjs](https://emulatorjs.org/) for playing lots of roms inside the app without having to deal with external emulators + +### Credits + +- UI Sounds + - [CC BY 4.0 - Credit: JC Sounds](https://opengameart.org/content/jc-sounds-ui-utility-pack-vol-1) + - [Sounds by: Chhoff](https://chhoffmusic.itch.io/classic-ui-sfx) diff --git a/bun.lock b/bun.lock index edc9367..b111ebe 100644 --- a/bun.lock +++ b/bun.lock @@ -50,6 +50,7 @@ "@tanstack/router-plugin": "^1.157.16", "@tanstack/zod-adapter": "^1.162.4", "@types/adm-zip": "^0.5.8", + "@types/audiosprite": "^0.7.3", "@types/bun": "latest", "@types/fs-extra": "^11.0.4", "@types/howler": "^2.2.12", @@ -63,6 +64,7 @@ "adm-zip": "^0.5.16", "animate.css": "^4.1.1", "app-builder-bin": "^5.0.0-alpha.13", + "audiosprite": "^0.7.2", "babel-plugin-react-compiler": "^1.0.0", "classnames": "^2.5.1", "concurrently": "^9.2.1", @@ -590,6 +592,8 @@ "@types/adm-zip": ["@types/adm-zip@0.5.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q=="], + "@types/audiosprite": ["@types/audiosprite@0.7.3", "", {}, "sha512-P4rUuHPt2kWPMqyObfh1SfqS2H/ZuTxByh00ecuI2tOdvP5b8NznuBeQgemDXV9v8b4pewFPB9G3BuYRONqD7A=="], + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], @@ -674,12 +678,14 @@ "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], - "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + "async": ["async@0.9.2", "", {}, "sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], "atomically": ["atomically@2.1.0", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q=="], + "audiosprite": ["audiosprite@0.7.2", "", { "dependencies": { "async": "~0.9.0", "glob": "^6.0.4", "mkdirp": "^0.5.0", "optimist": "~0.6.1", "underscore": "~1.8.3", "winston": "~1.0.0" }, "bin": { "audiosprite": "./cli.js" } }, "sha512-9Z6UwUuv4To5nUQNRIw5/Q3qA7HYm0ANzoW5EDGPEsU2oIRVgmIlLlm9YZfpPKoeUxt54vMStl2/762189VmJw=="], + "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="], "axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="], @@ -762,6 +768,8 @@ "colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="], + "colors": ["colors@1.0.3", "", {}, "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], @@ -840,6 +848,8 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "cycle": ["cycle@1.0.3", "", {}, "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA=="], + "daisyui": ["daisyui@5.5.14", "", {}, "sha512-L47rvw7I7hK68TA97VB8Ee0woHew+/ohR6Lx6Ah/krfISOqcG4My7poNpX5Mo5/ytMxiR40fEaz6njzDi7cuSg=="], "dargs": ["dargs@7.0.0", "", {}, "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg=="], @@ -954,6 +964,8 @@ "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + "eyes": ["eyes@0.1.8", "", {}, "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ=="], + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -1016,7 +1028,7 @@ "gitconfiglocal": ["gitconfiglocal@1.0.0", "", { "dependencies": { "ini": "^1.3.2" } }, "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ=="], - "glob": ["glob@10.3.3", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.0.3", "minimatch": "^9.0.1", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", "path-scurry": "^1.10.1" }, "bin": { "glob": "dist/cjs/src/bin.js" } }, "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw=="], + "glob": ["glob@6.0.4", "", { "dependencies": { "inflight": "^1.0.4", "inherits": "2", "minimatch": "2 || 3", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A=="], "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -1066,6 +1078,8 @@ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], @@ -1104,6 +1118,8 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="], + "jackspeak": ["jackspeak@2.3.6", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ=="], "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="], @@ -1278,12 +1294,16 @@ "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], "opener": ["opener@1.5.2", "", { "bin": { "opener": "bin/opener-bin.js" } }, "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="], + "optimist": ["optimist@0.6.1", "", { "dependencies": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" } }, "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], @@ -1308,6 +1328,8 @@ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], @@ -1332,6 +1354,8 @@ "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + "pkginfo": ["pkginfo@0.3.1", "", {}, "sha512-yO5feByMzAp96LtP58wvPKSbaKAi/1C4kV9XpTctr6EepnP6F33RBNOiVrdz9BrPA98U2BMFsTNHo44TWcbQ2A=="], + "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], "portfinder": ["portfinder@1.0.38", "", { "dependencies": { "async": "^3.2.6", "debug": "^4.3.6" } }, "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg=="], @@ -1516,6 +1540,8 @@ "split2": ["split2@3.2.2", "", { "dependencies": { "readable-stream": "^3.0.0" } }, "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="], + "stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="], + "standard-version": ["standard-version@9.5.0", "", { "dependencies": { "chalk": "^2.4.2", "conventional-changelog": "3.1.25", "conventional-changelog-config-spec": "2.1.0", "conventional-changelog-conventionalcommits": "4.6.3", "conventional-recommended-bump": "6.1.0", "detect-indent": "^6.0.0", "detect-newline": "^3.1.0", "dotgitignore": "^2.1.0", "figures": "^3.1.0", "find-up": "^5.0.0", "git-semver-tags": "^4.0.0", "semver": "^7.1.1", "stringify-package": "^1.0.1", "yargs": "^16.0.0" }, "bin": { "standard-version": "bin/cli.js" } }, "sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q=="], "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1618,6 +1644,8 @@ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + "underscore": ["underscore@1.8.3", "", {}, "sha512-5WsVTFcH1ut/kkhAaHf4PVgI8c7++GiVcpCGxPouI6ZVjsqPnSDf8h/8HtVqc0t4fzRXwnMK70EcZeAs3PIddg=="], + "undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="], "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], @@ -1672,12 +1700,16 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], + "winston": ["winston@1.0.2", "", { "dependencies": { "async": "~1.0.0", "colors": "1.0.x", "cycle": "1.0.x", "eyes": "0.1.x", "isstream": "0.1.x", "pkginfo": "0.3.x", "stack-trace": "0.0.x" } }, "sha512-BLxJH3KCgJ2paj2xKYTQLpxdKr9URPDDDLJnRVcbud7izT+m8Xzt5Rod6mnNgEcfT0fRvhEy2Cj3cEnnQpa6qA=="], + + "wordwrap": ["wordwrap@0.0.3", "", {}, "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw=="], "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], @@ -1752,6 +1784,8 @@ "@jimp/wasm-webp/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@node-minify/core/glob": ["glob@10.3.3", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.0.3", "minimatch": "^9.0.1", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", "path-scurry": "^1.10.1" }, "bin": { "glob": "dist/cjs/src/bin.js" } }, "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw=="], + "@node-minify/core/mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], "@node-minify/terser/terser": ["terser@5.36.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w=="], @@ -1808,10 +1842,12 @@ "gitconfiglocal/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], - "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + "glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "handlebars/wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], + "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "html-encoding-sniffer/whatwg-encoding": ["whatwg-encoding@2.0.0", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="], @@ -1832,6 +1868,8 @@ "nypm/citty": ["citty@0.2.0", "", {}, "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA=="], + "optimist/minimist": ["minimist@0.0.10", "", {}, "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -1840,6 +1878,8 @@ "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], + "portfinder/async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + "read-pkg/normalize-package-data": ["normalize-package-data@2.5.0", "", { "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="], "read-pkg-up/find-up": ["find-up@2.1.0", "", { "dependencies": { "locate-path": "^2.0.0" } }, "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ=="], @@ -1870,6 +1910,8 @@ "vite-static-assets-plugin/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "winston/async": ["async@1.0.0", "", {}, "sha512-5mO7DX4CbJzp9zjaFXusQQ4tzKJARjNB1Ih1pVBi8wkbmXy/xzIDgEMXxWePLzt2OdFwaxfneIlT1nCiXubrPQ=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -1924,6 +1966,8 @@ "@jimp/core/file-type/token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="], + "@node-minify/core/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + "@node-minify/terser/terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], @@ -1938,8 +1982,6 @@ "get-pkg-repo/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], - "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "meow/read-pkg-up/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -2068,6 +2110,8 @@ "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + "@node-minify/core/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "dotgitignore/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], "dotgitignore/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], diff --git a/package.json b/package.json index 79ddcec..7b6b2e6 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "version:generate": "standard-version --sign", "package:Linux": "bun run build:prod:appimage", "package:Windows": "bun run build:prod", - "download:chromium": "bun scripts/download-chromium.ts --out=./bin/chromium" + "download:chromium": "bun scripts/download-chromium.ts --out=./bin/chromium", + "build:audiosprites": "bun ./scripts/generate-audio-sprites.ts" }, "dependencies": { "7zip-bin": "^5.2.0", @@ -88,6 +89,7 @@ "@tanstack/router-plugin": "^1.157.16", "@tanstack/zod-adapter": "^1.162.4", "@types/adm-zip": "^0.5.8", + "@types/audiosprite": "^0.7.3", "@types/bun": "latest", "@types/fs-extra": "^11.0.4", "@types/howler": "^2.2.12", @@ -101,6 +103,7 @@ "adm-zip": "^0.5.16", "animate.css": "^4.1.1", "app-builder-bin": "^5.0.0-alpha.13", + "audiosprite": "^0.7.2", "babel-plugin-react-compiler": "^1.0.0", "classnames": "^2.5.1", "concurrently": "^9.2.1", @@ -129,4 +132,4 @@ "vite-static-assets-plugin": "^1.2.2", "vite-tsconfig-paths": "^6.1.1" } -} +} \ No newline at end of file diff --git a/scripts/generate-audio-sprites.ts b/scripts/generate-audio-sprites.ts new file mode 100644 index 0000000..bcce3e8 --- /dev/null +++ b/scripts/generate-audio-sprites.ts @@ -0,0 +1,24 @@ +import audioSprite from 'audiosprite'; +import { $, which } from 'bun'; +import fs from "node:fs/promises"; +import path from 'node:path'; + +var files = await Array.fromAsync(new Bun.Glob('*.{ogg,wav}').scan({ cwd: './src/sounds' })); +console.log("Loaded", files.join(",")); + +await new Promise((resolve) => +{ + audioSprite( + files.map(f => path.join(path.resolve('./src/sounds'), f)), + { + output: path.resolve('./src/mainview/assets/sounds'), + path: path.resolve('./src/sounds'), + format: 'howler', + export: 'ogg' + }, async function (err, obj: any) + { + if (err) return console.error(err); + delete obj.urls; + Bun.file('./src/mainview/assets/sounds.json').write(JSON.stringify(obj, null, 2)).then(r => resolve(true)); + }); +}); \ No newline at end of file diff --git a/src/mainview/App.tsx b/src/mainview/App.tsx new file mode 100644 index 0000000..fb0d2db --- /dev/null +++ b/src/mainview/App.tsx @@ -0,0 +1,48 @@ +import { getCurrentFocusKey } from "@noriginmedia/norigin-spatial-navigation"; +import { Router } from "."; +import { useEffect } from "react"; +import audioCallbacks from "./scripts/audio/audioCallbacks"; +import { client as rommClient } from "../clients/romm/client.gen"; +import { RPC_URL } from "@/shared/constants"; + +export const focusQueue: string[] = []; + +export default function App (data: { children: any; }) +{ + + useEffect(() => + { + const focusMap = new Map(); + rommClient.setConfig({ + baseUrl: `${RPC_URL(__HOST__)}/api/romm`, + credentials: "include", + mode: "cors", + }); + + const unsub = Router.history.subscribe((op) => + { + if (op.action.type === 'PUSH') + { + focusMap.set(op.location.state.__TSR_index - 1, getCurrentFocusKey()); + } else if (op.action.type === 'BACK') + { + if (focusMap.has(op.location.state.__TSR_index)) + { + focusQueue.pop(); + focusQueue.push(focusMap.get(op.location.state.__TSR_index)!); + focusMap.delete(op.location.state.__TSR_index); + } + } + }); + + const audio = audioCallbacks(); + + return () => + { + unsub(); + audio.cleanup(); + }; + }, []); + + return <>{data.children}; +} \ No newline at end of file diff --git a/src/mainview/assets/sounds.json b/src/mainview/assets/sounds.json new file mode 100644 index 0000000..ae3bae3 --- /dev/null +++ b/src/mainview/assets/sounds.json @@ -0,0 +1,304 @@ +{ + "sprite": { + "Classic UI SFX - Chords #1": [ + 0, + 4005.215419501134 + ], + "Classic UI SFX - Chords #10": [ + 6000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #11": [ + 12000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #12": [ + 18000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #13": [ + 24000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #14": [ + 30000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #15": [ + 36000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #16": [ + 42000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #17": [ + 48000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #18": [ + 54000, + 4005.215419501134 + ], + "Classic UI SFX - Chords #19": [ + 60000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #2": [ + 66000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #20": [ + 72000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #3": [ + 78000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #4": [ + 84000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #5": [ + 90000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #6": [ + 96000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #7": [ + 102000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #8": [ + 108000, + 4005.215419501127 + ], + "Classic UI SFX - Chords #9": [ + 114000, + 4005.215419501127 + ], + "Classic UI SFX - Short - High #1": [ + 120000, + 2546.893424036284 + ], + "Classic UI SFX - Short - High #10": [ + 124000, + 2552.0861678004535 + ], + "Classic UI SFX - Short - High #11": [ + 128000, + 2927.0975056689394 + ], + "Classic UI SFX - Short - High #12": [ + 132000, + 2927.0975056689394 + ], + "Classic UI SFX - Short - High #13": [ + 136000, + 3000 + ], + "Classic UI SFX - Short - High #14": [ + 140000, + 2802.0861678004394 + ], + "Classic UI SFX - Short - High #15": [ + 144000, + 2723.9455782312803 + ], + "Classic UI SFX - Short - High #16": [ + 148000, + 2927.0975056689394 + ], + "Classic UI SFX - Short - High #17": [ + 152000, + 2880.226757369627 + ], + "Classic UI SFX - Short - High #18": [ + 156000, + 2359.387755102034 + ], + "Classic UI SFX - Short - High #19": [ + 160000, + 3052.0861678004394 + ], + "Classic UI SFX - Short - High #2": [ + 165000, + 2843.7641723355964 + ], + "Classic UI SFX - Short - High #20": [ + 169000, + 2015.6462585034092 + ], + "Classic UI SFX - Short - High #21": [ + 173000, + 2005.215419501127 + ], + "Classic UI SFX - Short - High #22": [ + 177000, + 2489.5918367346894 + ], + "Classic UI SFX - Short - High #23": [ + 181000, + 2458.3446712018144 + ], + "Classic UI SFX - Short - High #24": [ + 185000, + 2093.7641723355964 + ], + "Classic UI SFX - Short - High #25": [ + 189000, + 2005.215419501127 + ], + "Classic UI SFX - Short - High #3": [ + 193000, + 2864.6031746031613 + ], + "Classic UI SFX - Short - High #4": [ + 197000, + 3031.2698412698464 + ], + "Classic UI SFX - Short - High #5": [ + 202000, + 2598.9795918367236 + ], + "Classic UI SFX - Short - High #6": [ + 206000, + 2427.0975056689394 + ], + "Classic UI SFX - Short - High #7": [ + 210000, + 2468.752834467125 + ], + "Classic UI SFX - Short - High #8": [ + 214000, + 2916.666666666657 + ], + "Classic UI SFX - Short - High #9": [ + 218000, + 2250 + ], + "Classic UI SFX - Short - Low #1": [ + 222000, + 2010.4308390022538 + ], + "Classic UI SFX - Short - Low #10": [ + 226000, + 3020.8390022675644 + ], + "Classic UI SFX - Short - Low #11": [ + 231000, + 2458.3446712018144 + ], + "Classic UI SFX - Short - Low #12": [ + 235000, + 2901.0430839002197 + ], + "Classic UI SFX - Short - Low #13": [ + 239000, + 2843.7641723355964 + ], + "Classic UI SFX - Short - Low #14": [ + 243000, + 3135.4195011337824 + ], + "Classic UI SFX - Short - Low #15": [ + 248000, + 2703.1292517006877 + ], + "Classic UI SFX - Short - Low #16": [ + 252000, + 2875.011337868472 + ], + "Classic UI SFX - Short - Low #17": [ + 256000, + 2927.0975056689394 + ], + "Classic UI SFX - Short - Low #18": [ + 260000, + 3057.2789115646515 + ], + "Classic UI SFX - Short - Low #19": [ + 265000, + 2473.9455782312803 + ], + "Classic UI SFX - Short - Low #2": [ + 269000, + 2583.3333333333144 + ], + "Classic UI SFX - Short - Low #20": [ + 273000, + 2515.646258503409 + ], + "Classic UI SFX - Short - Low #21": [ + 277000, + 2604.172335600879 + ], + "Classic UI SFX - Short - Low #22": [ + 281000, + 3031.2698412698182 + ], + "Classic UI SFX - Short - Low #23": [ + 286000, + 2937.50566893425 + ], + "Classic UI SFX - Short - Low #24": [ + 290000, + 2609.387755102034 + ], + "Classic UI SFX - Short - Low #25": [ + 294000, + 2625.0113378685 + ], + "Classic UI SFX - Short - Low #3": [ + 298000, + 2828.140589569159 + ], + "Classic UI SFX - Short - Low #4": [ + 302000, + 2614.6031746031895 + ], + "Classic UI SFX - Short - Low #5": [ + 306000, + 3161.4739229024735 + ], + "Classic UI SFX - Short - Low #6": [ + 311000, + 2333.3333333333144 + ], + "Classic UI SFX - Short - Low #7": [ + 315000, + 2536.4625850340303 + ], + "Classic UI SFX - Short - Low #8": [ + 319000, + 2630.2267573695985 + ], + "Classic UI SFX - Short - Low #9": [ + 323000, + 2697.936507936504 + ], + "UI_Single_Set 16_01": [ + 327000, + 309.5918367346826 + ], + "UI_Single_Set 16_02": [ + 329000, + 309.5918367346826 + ], + "UI_Single_Set 16_03": [ + 331000, + 309.5918367346826 + ], + "UI_TwoNote_Set 15_01": [ + 333000, + 335.2380952380827 + ], + "UI_TwoNote_Set 15_02": [ + 335000, + 309.5918367346826 + ] + } +} \ No newline at end of file diff --git a/src/mainview/assets/sounds.ogg b/src/mainview/assets/sounds.ogg new file mode 100644 index 0000000..b8e00d5 --- /dev/null +++ b/src/mainview/assets/sounds.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a3bb2f9a59e20e5ea49fec7fca68cda5c9167df332ff25d24c29870af834af7 +size 2229386 diff --git a/src/mainview/components/AutoFocus.tsx b/src/mainview/components/AutoFocus.tsx index 2744e8e..8c42502 100644 --- a/src/mainview/components/AutoFocus.tsx +++ b/src/mainview/components/AutoFocus.tsx @@ -1,5 +1,5 @@ import { doesFocusableExist, FocusDetails, getCurrentFocusKey } from "@noriginmedia/norigin-spatial-navigation"; -import { useEffect } from "react"; +import { useEffect, useLayoutEffect } from "react"; export function AutoFocus (data: { parentKey?: string; @@ -8,7 +8,7 @@ export function AutoFocus (data: { delay?: number; }) { - useEffect(() => + useLayoutEffect(() => { let delayTimeout: number | undefined; diff --git a/src/mainview/components/CardElement.tsx b/src/mainview/components/CardElement.tsx index 7f5f2dc..885d073 100644 --- a/src/mainview/components/CardElement.tsx +++ b/src/mainview/components/CardElement.tsx @@ -3,6 +3,7 @@ import classNames from "classnames"; import { JSX } from "react"; import { twMerge } from "tailwind-merge"; import useActiveControl from "../scripts/gamepads"; +import { oneShot } from "../scripts/audio/audio"; export function GameCardSkeleton () { @@ -38,10 +39,15 @@ export interface GameCardParams export default function CardElement (data: GameCardParams & InteractParams) { + const handleAction = () => + { + data.onAction?.(); + oneShot('click'); + }; const { ref, focused, focusSelf } = useFocusable({ focusKey: data.focusKey, onFocus: (l, p, details) => data.onFocus?.(data.id, ref.current as any, details), - onEnterPress: () => data.onAction?.(), + onEnterPress: handleAction, onBlur: () => data.onBlur?.(data.id), }); const { isPointer } = useActiveControl(); @@ -57,11 +63,10 @@ export default function CardElement (data: GameCardParams & InteractParams) scrollSnapAlign: isPointer ? "center" : "none" }} onFocus={focusSelf} - onDoubleClick={e => data.onAction?.(e.nativeEvent)} onClick={() => { focusSelf(); - data.onAction?.(); + handleAction(); }} className={twMerge( "relative game-card light:bg-base-100 dark:bg-base-300 flex flex-col z-5 overflow-hidden transition-all duration-200 not-mobile:drop-shadow-lg cursor-pointer focusable focusable-primary focusable-hover select-none focused focused:not-control-mouse:animate-wiggle focused:not-control-mouse:bg-base-content focused:not-control-mouse:text-base-300 focused:not-control-mouse:drop-shadow-lg focused:not-control-mouse:drop-shadow-black/30 focused:not-control-mouse:scale-102 focused:not-control-mouse:z-10 group control-mouse:hover:bg-base-200 h-full [--tw-border-style:inset] border-2 border-base-content/5 backdrop-opacity-0 active:bg-base-content! active:text-base-100 active:transition-none", diff --git a/src/mainview/components/CollectionList.tsx b/src/mainview/components/CollectionList.tsx index 8bb02a4..15b8d51 100644 --- a/src/mainview/components/CollectionList.tsx +++ b/src/mainview/components/CollectionList.tsx @@ -3,7 +3,7 @@ import { useSuspenseQuery } from "@tanstack/react-query"; import { CardList, GameMetaExtra } from "./CardList"; import { GameCardFocusHandler } from "./CardElement"; import { getCollectionsQuery } from "@queries/romm"; -import { Router } from ".."; +import { useRouter } from "@tanstack/react-router"; export default function CollectionList (data: { id: string, @@ -14,12 +14,13 @@ export default function CollectionList (data: { saveChildFocus?: 'session' | 'local'; }) { + const router = useRouter(); const { data: collections } = useSuspenseQuery(getCollectionsQuery); const handleDefaultSelect = (gameId: string) => { const [source, id] = gameId.split('@'); - Router.navigate({ + router.navigate({ to: `/collection/$source/$id`, params: { source, id }, search: { countHint: collections.find(c => c.id.id === id && c.id.source === source)?.game_count } diff --git a/src/mainview/components/CollectionsDetail.tsx b/src/mainview/components/CollectionsDetail.tsx index 35aae6f..ac0437f 100644 --- a/src/mainview/components/CollectionsDetail.tsx +++ b/src/mainview/components/CollectionsDetail.tsx @@ -1,18 +1,18 @@ -import { AnimatedBackground } from './AnimatedBackground'; -import { FocusContext, setFocus, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; -import { HeaderUI, StickyHeaderUI } from './Header'; +import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; +import { StickyHeaderUI } from './Header'; import { GameList } from './GameList'; import { Search, Settings2 } from 'lucide-react'; -import { JSX, Suspense, useEffect } from 'react'; +import { JSX, Suspense } from 'react'; import Shortcuts from './Shortcuts'; import { AutoFocus } from './AutoFocus'; import { GamePadButtonCode, useShortcutContext, useShortcuts } from '../scripts/shortcuts'; import { GameListFilterType } from '@/shared/constants'; import { GameCardFocusHandler } from './CardElement'; -import { HandleGoBack, useStickyDataAttr } from '../scripts/utils'; +import { HandleGoBack } from '../scripts/utils'; import LoadingCardList from './LoadingCardList'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { gameQuery } from '../scripts/queries/romm'; +import { useRouter } from '@tanstack/react-router'; export interface CollectionsDetailParams { @@ -29,6 +29,7 @@ export interface CollectionsDetailParams export function CollectionsDetail (data: CollectionsDetailParams) { + const router = useRouter(); const builtData = useQuery({ queryKey: ['filter', data.id], queryFn: async () => { @@ -42,7 +43,7 @@ export function CollectionsDetail (data: CollectionsDetailParams) preferredChildFocusKey: `${focusKey}-list` }); - useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: HandleGoBack }]); + useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: () => HandleGoBack(router) }], [router]); const { shortcuts } = useShortcutContext(); const handleScroll: GameCardFocusHandler = (cardId, node, details) => diff --git a/src/mainview/components/ContextDialog.tsx b/src/mainview/components/ContextDialog.tsx index bf9b757..94d31a8 100644 --- a/src/mainview/components/ContextDialog.tsx +++ b/src/mainview/components/ContextDialog.tsx @@ -6,6 +6,7 @@ import { X } from "lucide-react"; import { GamePadButtonCode, Shortcut, useShortcuts } from "../scripts/shortcuts"; import { ContextDialogContext } from "../scripts/contexts"; import { FOCUS_KEYS } from "../scripts/types"; +import { oneShot } from "../scripts/audio/audio"; export function ContextList (data: { options?: DialogEntry[]; @@ -34,6 +35,7 @@ export function OptionElement (data: DialogEntry & { onFocus?: () => void; class { if (data.disabled === true) return; data.action?.({ close: context.close, focus: focusSelf }); + oneShot('click'); }; const { ref, focusSelf, focusKey } = useFocusable({ focusKey: FOCUS_KEYS.CONTEXT_DIALOG_OPTION(context.id, data.id), @@ -57,6 +59,7 @@ export function OptionElement (data: DialogEntry & { onFocus?: () => void; class onClick={handleAction} data-selected={data.selected} aria-disabled={data.disabled} + data-sound-category={"menu"} className={ twMerge("flex cursor-pointer sm:text-sm md:text-base group-focusable scroll-m-4")}> @@ -100,10 +103,10 @@ export function useContextDialog (id: string, data: { content?: JSX.Element; cla data.onClose?.(); if (newSourceFocusKey) { - setFocus(newSourceFocusKey); + setFocus(newSourceFocusKey, { instant: true }); } else if (sourceFocusKey) { - setFocus(sourceFocusKey); + setFocus(sourceFocusKey, { instant: true }); } } @@ -137,12 +140,14 @@ export function ContextDialog (data: { const handleClose = () => { data.close(false); + oneShot('closeContext'); }; useEffect(() => { if (data.open) { - focusSelf(); + focusSelf({ instant: true }); + oneShot('openContext'); } }, [data.open]); diff --git a/src/mainview/components/Error.tsx b/src/mainview/components/Error.tsx index c9f0266..6bafd95 100644 --- a/src/mainview/components/Error.tsx +++ b/src/mainview/components/Error.tsx @@ -1,20 +1,20 @@ import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { Home, TriangleAlert } from "lucide-react"; import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/shortcuts"; -import { Router } from ".."; import Shortcuts from "./Shortcuts"; import { Button } from "./options/Button"; import { useEffect } from "react"; -import { ErrorComponentProps } from "@tanstack/react-router"; +import { ErrorComponentProps, useRouter } from "@tanstack/react-router"; export default function Error (data: ErrorComponentProps) { const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "not-found" }); - const handleReturn = () => Router.navigate({ to: '/', viewTransition: { types: ['zoom-in'] } }); + const router = useRouter(); + const handleReturn = () => router.navigate({ to: '/', viewTransition: { types: ['zoom-in'] } }); useShortcuts(focusKey, () => [{ label: "Return Home", button: GamePadButtonCode.B, action: handleReturn }]); const { shortcuts } = useShortcutContext(); - useEffect(() => { focusSelf(); }, []); + useEffect(() => { focusSelf({ instant: true }); }, []); return
diff --git a/src/mainview/components/Filters.tsx b/src/mainview/components/Filters.tsx index 7060bff..1dea7b6 100644 --- a/src/mainview/components/Filters.tsx +++ b/src/mainview/components/Filters.tsx @@ -8,6 +8,7 @@ import SvgIcon from "./SvgIcon"; import { twMerge } from "tailwind-merge"; import { useEffect } from "react"; import { GamePadButtonCode, useShortcuts } from "../scripts/shortcuts"; +import { oneShot } from "../scripts/audio/audio"; function FilterCat ( data: { @@ -19,7 +20,10 @@ function FilterCat ( { const { ref, focusSelf } = useFocusable({ focusKey: data.id, - onFocus: (l, p, details) => data.onFocus?.(data.id, ref.current, details), + onFocus: (l, p, details) => + { + data.onFocus?.(data.id, ref.current, details); + }, onEnterPress: data.onAction }); @@ -27,7 +31,8 @@ function FilterCat (
  • focusSelf({ event: e.nativeEvent })} + data-sound-category={data.active ? undefined : "filter"} className={"sm:text-sm sm:px-2 flex md:px-4 items-center justify-center rounded-full transition-all md:text-lg focusable focusable-primary hover:not-focused:not-aria-selected:bg-base-content/40 not-focused:cursor-pointer aria-selected:bg-base-content aria-selected:text-base-300 aria-selected:drop-shadow aria-selected:cursor-default active:bg-accent! active:text-accent-content! active:ring-offset-7 active:ring-offset-base-content select-none gap-1"} > {data.icon ? <>
    {data.icon}
    {data.children ?? data.label}
    :
    {data.children ?? data.label}
    } @@ -68,6 +73,10 @@ export function FilterUI (data: { if (!data.options[newFilter].selected) { data.setSelected(newFilter); + oneShot('selectFilter'); + } else + { + oneShot('invalidNavigation'); } }, button: GamePadButtonCode.R1 @@ -80,7 +89,13 @@ export function FilterUI (data: { const selectedFilterIndex = Math.max(0, filterIndex - 1,); const newFilter = filterKeys[selectedFilterIndex]; if (!data.options[newFilter].selected) + { data.setSelected(newFilter); + oneShot('selectFilter'); + } else + { + oneShot('invalidNavigation'); + } }, button: GamePadButtonCode.L1 }], [data.options]); @@ -90,7 +105,7 @@ export function FilterUI (data: { { if (hasFocusedChild) { - setFocus(`${data.id}-${defaultFocus}`); + setFocus(`${data.id}-${defaultFocus}`, { instant: true }); } }, [hasFocusedChild, defaultFocus, data.id]); diff --git a/src/mainview/components/FrontEndGameCard.tsx b/src/mainview/components/FrontEndGameCard.tsx index 533eb29..39bd6ab 100644 --- a/src/mainview/components/FrontEndGameCard.tsx +++ b/src/mainview/components/FrontEndGameCard.tsx @@ -1,15 +1,16 @@ import { RPC_URL } from "@/shared/constants"; import CardElement from "./CardElement"; -import { Router } from ".."; import { FileQuestion, HardDrive, Store } from "lucide-react"; import { JSX } from "react"; import { FOCUS_KEYS } from "../scripts/types"; +import { useRouter } from "@tanstack/react-router"; export default function FrontEndGameCard (data: { index: number, game: FrontEndGameType; showSource?: boolean; } & FocusParams & InteractParams) { + const router = useRouter(); function handleDefaultSelect (id: FrontEndId, source: string | null, sourceId: string | null) { - Router.navigate({ to: '/game/$source/$id', params: { id: String(sourceId ?? id.id), source: source ?? id.source } }); + router.navigate({ to: '/game/$source/$id', params: { id: String(sourceId ?? id.id), source: source ?? id.source } }); }; const platformUrl = new URL(`${RPC_URL(__HOST__)}${data.game.path_platform_cover}`); diff --git a/src/mainview/components/Header.tsx b/src/mainview/components/Header.tsx index 1dad439..76b87b6 100644 --- a/src/mainview/components/Header.tsx +++ b/src/mainview/components/Header.tsx @@ -14,7 +14,6 @@ import Bell, Bluetooth, Clock, - Plug, Settings, Wifi, WifiHigh, @@ -23,17 +22,15 @@ import } from "lucide-react"; import { RoundButton } from "./RoundButton"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; -import { RPC_URL, SystemInfoType } from "../../shared/constants"; import { JSX, RefObject, useContext, useEffect, useRef, useState } from "react"; -import { systemApi } from "../scripts/clientApi"; -import { Router } from ".."; import { useStickyDataAttr } from "../scripts/utils"; import { twMerge } from "tailwind-merge"; import { TwitchIcon } from "../scripts/brandIcons"; -import { rommLoggedInQuery, rommUserQuery } from "../scripts/queries/romm"; +import { rommLoggedInQuery } from "../scripts/queries/romm"; import { twitchLoginVerificationQuery } from "../scripts/queries/settings"; -import { da } from "zod/v4/locales"; import { SystemInfoContext } from "../scripts/contexts"; +import { useRouter } from "@tanstack/react-router"; +import { oneShot } from "../scripts/audio/audio"; function HeaderAvatar (data: { id: string; @@ -206,19 +203,23 @@ export function HeaderAccounts (data: { accounts?: HeaderAccount[]; }) placeholderData: keepPreviousData }); - const { ref } = useFocusable({ focusKey: 'accounts' }); + const handleSelect = () => + { + router.navigate({ to: '/settings/accounts' }); + oneShot('click'); + }; + const { ref } = useFocusable({ + focusKey: 'accounts', onEnterPress: handleSelect + }); const accounts: HeaderAccount[] = []; if (data.accounts) accounts.push(...data.accounts); + const router = useRouter(); if (rommUser.data?.hasLogin || rommUser.isError) { accounts.push({ id: 'romm', preview: `https://romm.app/_ipx/q_80/images/blocks/logos/romm.svg`, - action: () => - { - Router.navigate({ to: '/settings/accounts', search: { focus: 'rommAddress' } }); - }, className: rommUser.data?.hasLogin && !rommUser.isError ? undefined : "border-error", type: 'secondary' }); @@ -228,15 +229,11 @@ export function HeaderAccounts (data: { accounts?: HeaderAccount[]; }) { accounts.push({ id: 'twitch', preview: TwitchIcon, - action: () => - { - Router.navigate({ to: '/settings/accounts', search: { focus: 'rommAddress' } }); - }, type: 'secondary' }); } - return
    + return
    {accounts?.map(a => { - Router.navigate({ to: '/settings/accounts' }); + router.navigate({ to: '/settings/accounts' }); }; return ( @@ -296,7 +294,7 @@ export function HeaderUI (data: HeaderUIParams) className="flex items-center justify-between text-base-content" style={{ viewTimelineName: 'header' }} > - + {data.title} , id: "settings", action: goToSettings, external: true }]} /> diff --git a/src/mainview/components/LoadMoreButton.tsx b/src/mainview/components/LoadMoreButton.tsx index 5d2cb03..84db100 100644 --- a/src/mainview/components/LoadMoreButton.tsx +++ b/src/mainview/components/LoadMoreButton.tsx @@ -13,6 +13,7 @@ export default function LoadMoreButton (data: { isFetching: boolean; lastId?: Fr }; const { ref, focusKey, focused } = useFocusable({ + focusable: !data.isFetching, focusKey: 'load-more-btn', onFocus: (_l, _p, details) => data.onFocus?.(focusKey, ref.current, details), onEnterPress: handleAction diff --git a/src/mainview/components/NotFound.tsx b/src/mainview/components/NotFound.tsx index ea70534..0172729 100644 --- a/src/mainview/components/NotFound.tsx +++ b/src/mainview/components/NotFound.tsx @@ -1,19 +1,20 @@ import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { Home, TriangleAlert } from "lucide-react"; import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/shortcuts"; -import { Router } from ".."; import Shortcuts from "./Shortcuts"; import { Button } from "./options/Button"; import { useEffect } from "react"; +import { useRouter } from "@tanstack/react-router"; export default function NotFound () { const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "not-found" }); - const handleReturn = () => Router.navigate({ to: '/', viewTransition: { types: ['zoom-in'] } }); + const router = useRouter(); + const handleReturn = () => router.navigate({ to: '/', viewTransition: { types: ['zoom-in'] } }); useShortcuts(focusKey, () => [{ label: "Return Home", button: GamePadButtonCode.B, action: handleReturn }]); const { shortcuts } = useShortcutContext(); - useEffect(() => { focusSelf(); }, []); + useEffect(() => { focusSelf({ instant: true }); }, []); return
    diff --git a/src/mainview/components/Screenshots.tsx b/src/mainview/components/Screenshots.tsx index 3689b61..ae5af90 100644 --- a/src/mainview/components/Screenshots.tsx +++ b/src/mainview/components/Screenshots.tsx @@ -83,7 +83,7 @@ export default function Screenshots (data: { screenshots?: string[]; className?: const closest = findClosestElementToCenter(scrollRef.current); if (!closest) return; const closestIndex = Array.from(scrollRef.current.children).indexOf(closest); - setFocus(`screenshot-${closestIndex}`); + setFocus(`screenshot-${closestIndex}`, { instant: true }); } }, [focused, hasFocusedChild, scrollRef.current]); diff --git a/src/mainview/components/game/ActionButtons.tsx b/src/mainview/components/game/ActionButtons.tsx index 3a7eb98..d931fa6 100644 --- a/src/mainview/components/game/ActionButtons.tsx +++ b/src/mainview/components/game/ActionButtons.tsx @@ -9,8 +9,7 @@ import MainActions from "./MainActions"; import ActionButton from "./ActionButton"; import { useLocalStorage } from "usehooks-ts"; import FocusTooltip from "../FocusTooltip"; -import { Router } from "@/mainview"; -import { useBlocker } from "@tanstack/react-router"; +import { useBlocker, useRouter } from "@tanstack/react-router"; function AchievementsInfo (data: { game: FrontEndGameTypeDetailed; } & InteractParams) { @@ -35,11 +34,12 @@ export default function ActionButtons (data: { game?: FrontEndGameTypeDetailed, const [, setDetailsSection] = useLocalStorage('details-section', 'screenshots'); const { ref, focusKey, hasFocusedChild } = useFocusable({ focusKey: 'actions', forceFocus: true, trackChildren: true, preferredChildFocusKey: 'mainAction' }); + const router = useRouter(); const deleteMutation = useMutation({ ...deleteGameMutation({ id: data.id, source: data.source }), onSuccess: (d, v, r, ctx) => { - ctx.client.invalidateQueries(gameInvalidationQuery(data.id, data.source)).then(() => Router.history.back()); + ctx.client.invalidateQueries(gameInvalidationQuery(data.id, data.source)).then(() => router.history.back()); }, onError (error) { diff --git a/src/mainview/components/game/MainActions.tsx b/src/mainview/components/game/MainActions.tsx index 1e82ea1..e89f910 100644 --- a/src/mainview/components/game/MainActions.tsx +++ b/src/mainview/components/game/MainActions.tsx @@ -1,4 +1,3 @@ -import { Router } from "@/mainview"; import { rommApi } from "@/mainview/scripts/clientApi"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { JSX, useEffect, useRef, useState } from "react"; @@ -9,10 +8,12 @@ import { ContextList, DialogEntry, useContextDialog } from "../ContextDialog"; import { Clock, Download, EllipsisVertical, Import, PackageOpen, Play, TriangleAlert } from "lucide-react"; import { gameInvalidationQuery, installMutation, playMutation } from "@/mainview/scripts/queries/romm"; import ActionButton from "./ActionButton"; +import { useRouter } from "@tanstack/react-router"; export default function MainActions (data: { game?: FrontEndGameTypeDetailed, source: string, id: string; }) { const installMut = useMutation(installMutation(data.source, data.id)); + const router = useRouter(); const playMut = useMutation({ ...playMutation, onError (error) { @@ -20,7 +21,7 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so }, onSuccess (data, { source, id }, onMutateResult, context) { - Router.navigate({ to: '/launcher/$source/$id', params: { source: source, id: id }, replace: true }); + router.navigate({ to: '/launcher/$source/$id', params: { source: source, id: id }, replace: true }); }, }); const ws = useRef<{ send: (data: string) => void; }>(undefined); @@ -58,10 +59,10 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so { if (localId) { - Router.navigate({ to: '/game/$source/$id', params: { id: String(localId), source: 'local' }, replace: true }); + router.navigate({ to: '/game/$source/$id', params: { id: String(localId), source: 'local' }, replace: true }); } else { - Router.navigate({ to: '/game/$source/$id', params: { id: data.id, source: data.source }, replace: true }); + router.navigate({ to: '/game/$source/$id', params: { id: data.id, source: data.source }, replace: true }); } }); } else if (e.data.status === 'error') @@ -78,7 +79,7 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so sub.close(); ws.current = undefined; }; - }, [data.source, data.id]); + }, [data.source, data.id, router]); let progressIcon: JSX.Element | undefined = undefined; switch (status) @@ -107,7 +108,7 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so if (cmd.emulator === 'EMULATORJS') { const params = new URLSearchParams(cmd.command); - Router.navigate({ to: '/embedded/$source/$id', params: { source: data.source, id: data.id }, search: Object.fromEntries(params.entries()), replace: true }); + router.navigate({ to: '/embedded/$source/$id', params: { source: data.source, id: data.id }, search: Object.fromEntries(params.entries()), replace: true }); } else { playMut.mutate({ source: data.source, id: data.id, command_id: cmd.id }); @@ -142,7 +143,7 @@ export default function MainActions (data: { game?: FrontEndGameTypeDetailed, so { if (status === 'missing-emulator') { - Router.navigate({ to: '/settings/directories' }); + router.navigate({ to: '/settings/directories' }); } }} id="mainAction"> diff --git a/src/mainview/components/options/Button.tsx b/src/mainview/components/options/Button.tsx index 1c6e63c..faef3fa 100644 --- a/src/mainview/components/options/Button.tsx +++ b/src/mainview/components/options/Button.tsx @@ -7,6 +7,7 @@ import import classNames from "classnames"; import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts"; import { CSSProperties } from "react"; +import { oneShot } from "@/mainview/scripts/audio/audio"; export type ButtonStyle = 'base' | 'accent' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'; @@ -35,9 +36,14 @@ export function Button (data: { tooltipType?: "base" | "accent" | "error"; } & InteractParams & FocusParams) { + const handleAction = (e?: any) => + { + data.onAction?.(e); + oneShot('click'); + }; const { ref, focused, focusKey } = useFocusable({ focusKey: data.id, - onEnterPress: data.onAction, + onEnterPress: () => handleAction(), onFocus: (_l, _p, details) => data.onFocus?.(focusKey, ref.current, details), focusable: !data.disabled }); @@ -49,7 +55,7 @@ export function Button (data: { return + {open && ({ diff --git a/src/mainview/components/options/OptionInput.tsx b/src/mainview/components/options/OptionInput.tsx index 1f43246..2181b93 100644 --- a/src/mainview/components/options/OptionInput.tsx +++ b/src/mainview/components/options/OptionInput.tsx @@ -4,6 +4,7 @@ import { useOptionContext } from "./OptionSpace"; import { useFocusable } from "@noriginmedia/norigin-spatial-navigation"; import { systemApi } from "../../scripts/clientApi"; import { CheckIcon, X } from "lucide-react"; +import { oneShot } from "@/mainview/scripts/audio/audio"; export function OptionInput (data: { name: string; @@ -27,6 +28,7 @@ export function OptionInput (data: { { inputRef.current?.focus(); } + oneShot('click'); }; const { ref } = useFocusable({ focusKey: data.name, onEnterPress: handlePress @@ -79,12 +81,14 @@ export function OptionInput (data: { name={data.name} checked={Boolean(data.value)} type={data.type} + onClick={() => { oneShot("click"); }} autoComplete={data.autocomplete} onFocus={handleFocus} placeholder={data.placeholder} onChange={e => data.onChange?.(e.target.checked)} onBlur={data.onBlur} className={twMerge( + "active:bg-base-content rounded-full", data.className )} /> diff --git a/src/mainview/components/options/PathSettingsOption.tsx b/src/mainview/components/options/PathSettingsOption.tsx index 29ba634..395767e 100644 --- a/src/mainview/components/options/PathSettingsOption.tsx +++ b/src/mainview/components/options/PathSettingsOption.tsx @@ -80,7 +80,7 @@ export function PathSettingsOptionBase (data: PathSettingsOptionParams & { const handleCloseSeatch = () => { setIsBrowsing(false); - setFocus(`${data.id}-browse`); + setFocus(`${data.id}-browse`, { instant: true }); }; const handleInputBlur = () => diff --git a/src/mainview/components/store/EmulatorsSection.tsx b/src/mainview/components/store/EmulatorsSection.tsx index a846406..93a0419 100644 --- a/src/mainview/components/store/EmulatorsSection.tsx +++ b/src/mainview/components/store/EmulatorsSection.tsx @@ -8,12 +8,12 @@ import { ChevronRight, Joystick } from "lucide-react"; import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts"; import { scrollIntoNearestParent, useDragScroll } from "@/mainview/scripts/utils"; import FocusDots from "../FocusDots"; -import { Router } from "@/mainview"; import { StoreEmulatorCard } from "./StoreEmulatorCard"; import { FOCUS_KEYS } from "@/mainview/scripts/types"; import Carousel from "../Carousel"; +import { useRouter } from "@tanstack/react-router"; -function SeeAllCard (data: { id: string; onAction: () => void; onFocus?: (details: { node: HTMLElement, instant: boolean; }) => void; }) +function SeeAllCard (data: { id: string; onAction: () => void; onFocus?: (details: { node: HTMLElement, instant?: boolean; }) => void; }) { const { ref, focusKey } = useFocusable({ focusKey: data.id, @@ -39,6 +39,7 @@ export function EmulatorsSection (data: { header?: any; } & FocusParams) { + const router = useRouter(); const { ref, focusKey } = useFocusable({ focusKey: FOCUS_KEYS.EMULATOR_SECTION(data.id), trackChildren: true, @@ -68,7 +69,7 @@ export function EmulatorsSection (data: { scrollIntoNearestParent(node, { behavior: details.instant ? 'instant' : 'smooth' }); }} /> )) ?? Array.from({ length: 8 }).map((_, i) =>
    )} - Router.navigate({ to: '/store/tab/emulators', viewTransition: { types: ['zoom-in'] } })} onFocus={({ node, instant }) => scrollIntoNearestParent(node, { behavior: instant ? 'instant' : 'smooth' })} /> + router.navigate({ to: '/store/tab/emulators', viewTransition: { types: ['zoom-in'] } })} onFocus={({ node, instant }) => scrollIntoNearestParent(node, { behavior: instant ? 'instant' : 'smooth' })} /> diff --git a/src/mainview/components/store/GamesSection.tsx b/src/mainview/components/store/GamesSection.tsx index 54f4057..843e8e7 100644 --- a/src/mainview/components/store/GamesSection.tsx +++ b/src/mainview/components/store/GamesSection.tsx @@ -30,7 +30,7 @@ export function GamesSection (data: { useEffect(() => { if (focused) - focusSelf(); + focusSelf({ instant: true }); }, [!!data.games]); return ( diff --git a/src/mainview/components/store/MissingEmulatorsSection.tsx b/src/mainview/components/store/MissingEmulatorsSection.tsx index b064ec8..fc5efd8 100644 --- a/src/mainview/components/store/MissingEmulatorsSection.tsx +++ b/src/mainview/components/store/MissingEmulatorsSection.tsx @@ -9,6 +9,7 @@ import { ChevronRight, CircleQuestionMark, SearchAlert } from "lucide-react"; import { GamePadButtonCode, useShortcuts } from "@/mainview/scripts/shortcuts"; import { RPC_URL } from "@/shared/constants"; import { FOCUS_KEYS } from "@/mainview/scripts/types"; +import { oneShot } from "@/mainview/scripts/audio/audio"; // ── Single missing-emulator card ─────────────────────────────────────────── interface MissingCardProps @@ -19,7 +20,11 @@ interface MissingCardProps function MissingCard ({ emulator: em, onSelect }: MissingCardProps) { - const handleSelect = () => onSelect?.(em.name, focusKey); + const handleSelect = () => + { + onSelect?.(em.name, focusKey); + oneShot('click'); + }; const { ref, focusKey } = useFocusable({ focusKey: FOCUS_KEYS.MISSING_CARD(em.name), diff --git a/src/mainview/components/store/StoreEmulatorCard.tsx b/src/mainview/components/store/StoreEmulatorCard.tsx index ccf81cb..202c38c 100644 --- a/src/mainview/components/store/StoreEmulatorCard.tsx +++ b/src/mainview/components/store/StoreEmulatorCard.tsx @@ -9,6 +9,7 @@ import { BadgeCheck, ChevronRight, EllipsisVertical, FileQuestion, IceCream2, Pa import { FOCUS_KEYS } from "@/mainview/scripts/types"; import { FlatpackIcon } from "@/mainview/scripts/brandIcons"; import { JSX } from "react"; +import { oneShot } from "@/mainview/scripts/audio/audio"; export const emulatorStatusIcons: Record = { store: , @@ -26,7 +27,11 @@ export function StoreEmulatorCard (data: { className?: string; }) { - const handleSelect = () => data.onSelect?.(data.emulator.name, focusKey); + const handleSelect = () => + { + data.onSelect?.(data.emulator.name, focusKey); + oneShot('click'); + }; const { ref, focusKey } = useFocusable({ focusKey: FOCUS_KEYS.EMULATOR_CARD(data.id), @@ -45,6 +50,7 @@ export function StoreEmulatorCard (data: { ref={ref} role="button" tabIndex={0} + data-sound-category="emulator" data-installed={data.emulator.validSources.some(s => s.exists)} onClick={isTouch ? handleSelect : undefined} className={twMerge("relative focusable focusable-info bg-base-100 rounded-4xl transition-shadow focused:not-control-mouse:animate-scale-small shadow-lg border border-base-content/10 active:ring-4 active:ring-base-content active:transition-none", data.className)} @@ -87,7 +93,7 @@ export function StoreEmulatorCard (data: {
    ; })} {isMouse && <> - + }
    diff --git a/src/mainview/gen/static-icon-assets.gen.ts b/src/mainview/gen/static-icon-assets.gen.ts index cb3fe1b..1d1a4aa 100644 --- a/src/mainview/gen/static-icon-assets.gen.ts +++ b/src/mainview/gen/static-icon-assets.gen.ts @@ -464,7 +464,7 @@ const assets = new Set([ ]); // Store basePath resolved from Vite config -const BASE_PATH = "./"; +const BASE_PATH = "/"; /** diff --git a/src/mainview/index.tsx b/src/mainview/index.tsx index c76e8e9..f5639f9 100644 --- a/src/mainview/index.tsx +++ b/src/mainview/index.tsx @@ -9,15 +9,13 @@ import } from "@tanstack/react-router"; import { routeTree } from "./gen/routeTree.gen"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { RPC_URL } from "../shared/constants"; import "./scripts/gamepads"; import "./scripts/windowEvents"; -import { client as rommClient } from "../clients/romm/client.gen"; import "./scripts/spatialNavigation"; import NotFound from "./components/NotFound"; import Error from "./components/Error"; import serviceWorker from './scripts/serviceWorker?worker&url'; -import { getCurrentFocusKey, setFocus } from "@noriginmedia/norigin-spatial-navigation"; +import App from "./App"; if ('serviceWorker' in navigator) { @@ -26,12 +24,6 @@ if ('serviceWorker' in navigator) const hashHistory = createHashHistory({}); -rommClient.setConfig({ - baseUrl: `${RPC_URL(__HOST__)}/api/romm`, - credentials: "include", - mode: "cors", -}); - const queryClient = new QueryClient(); export interface RouterContext @@ -66,25 +58,6 @@ export const Router = createRouter({ } }); -const focusMap = new Map(); -export const focusQueue: string[] = []; - -Router.history.subscribe((op) => -{ - if (op.action.type === 'PUSH') - { - focusMap.set(op.location.state.__TSR_index - 1, getCurrentFocusKey()); - } else if (op.action.type === 'BACK') - { - if (focusMap.has(op.location.state.__TSR_index)) - { - focusQueue.pop(); - focusQueue.push(focusMap.get(op.location.state.__TSR_index)!); - focusMap.delete(op.location.state.__TSR_index); - } - } -}); - // Register things for typesafety declare module "@tanstack/react-router" { interface Register @@ -100,9 +73,11 @@ if (!rootElement.innerHTML) const root = createRoot(rootElement); root.render( - - - + + + + + , ); } diff --git a/src/mainview/routes/__root.tsx b/src/mainview/routes/__root.tsx index 345a707..2687614 100644 --- a/src/mainview/routes/__root.tsx +++ b/src/mainview/routes/__root.tsx @@ -4,10 +4,7 @@ import Notifications from "../components/Notifications"; import { Toaster } from "react-hot-toast"; import { mobileCheck, useLocalSetting } from "../scripts/utils"; import useActiveControl from "../scripts/gamepads"; -import { useEffect, useState } from "react"; -import { SystemInfoContext } from "../scripts/contexts"; -import { SystemInfoType } from "@/shared/constants"; -import { systemApi } from "../scripts/clientApi"; +import { useEffect } from "react"; import AppCommunication from "../components/AppCommunication"; export const Route = createRootRouteWithContext()({ diff --git a/src/mainview/routes/embedded.$source.$id.tsx b/src/mainview/routes/embedded.$source.$id.tsx index 9d67605..7b7fa7d 100644 --- a/src/mainview/routes/embedded.$source.$id.tsx +++ b/src/mainview/routes/embedded.$source.$id.tsx @@ -1,9 +1,8 @@ import { RPC_URL, SERVER_URL } from '@/shared/constants'; -import { createFileRoute } from '@tanstack/react-router'; +import { createFileRoute, useRouter } from '@tanstack/react-router'; import { zodValidator } from '@tanstack/zod-adapter'; import z from 'zod'; import { RefObject, useEffect, useRef, useState } from 'react'; -import { Router } from '..'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { ButtonStyle } from '../components/options/Button'; import { DoorOpen, RefreshCw, Undo } from 'lucide-react'; @@ -57,7 +56,7 @@ function Overlay (data: { { if (data.open) { - focusSelf(); + focusSelf({ instant: true }); } }, [data.open]); @@ -122,6 +121,7 @@ function Frame (data: { ref: RefObject; }) function RouteComponent () { + const router = useRouter(); const { ref, focusSelf, focusKey } = useFocusable({ focusKey: 'emulatorjs', preferredChildFocusKey: 'frame', @@ -133,7 +133,7 @@ function RouteComponent () function HandleGoBack () { - Router.navigate({ to: '/game/$source/$id', params: { source, id }, replace: true }); + router.navigate({ to: '/game/$source/$id', params: { source, id }, replace: true }); } useEventListener('message', e => @@ -173,7 +173,7 @@ function RouteComponent () }; useEffect(() => setPaused(overlayOpen), [overlayOpen]); const { shortcuts } = useShortcutContext(); - useEffect(() => { if (!overlayOpen) focusSelf(); }, [overlayOpen]); + useEffect(() => { if (!overlayOpen) focusSelf({ instant: true }); }, [overlayOpen]); function handleClose () { setOverlayOpen(false); diff --git a/src/mainview/routes/game/$source.$id.tsx b/src/mainview/routes/game/$source.$id.tsx index 6f97fbc..7147e79 100644 --- a/src/mainview/routes/game/$source.$id.tsx +++ b/src/mainview/routes/game/$source.$id.tsx @@ -1,16 +1,15 @@ -import { createFileRoute, ErrorComponentProps } from "@tanstack/react-router"; +import { createFileRoute, ErrorComponentProps, useRouter, useRouterState } from "@tanstack/react-router"; import { RPC_URL } from "@shared/constants"; import { useEffect, useRef, useState } from "react"; -import { FocusContext, setFocus, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; -import { Calendar, Clock, Folder, Gamepad2, Image, Info, Store, TriangleAlert, Trophy } from "lucide-react"; +import { FocusContext, useFocusable } from "@noriginmedia/norigin-spatial-navigation"; +import { Calendar, Folder, Gamepad2, Image, Info, TriangleAlert, Trophy } from "lucide-react"; import { HeaderUI } from "../../components/Header"; import { AnimatedBackground } from "../../components/AnimatedBackground"; import { useQuery } from "@tanstack/react-query"; -import { Router } from "../.."; import Shortcuts from "../../components/Shortcuts"; import { GamePadButtonCode, useShortcutContext, useShortcuts } from "@/mainview/scripts/shortcuts"; import Screenshots from "@/mainview/components/Screenshots"; -import { HandleGoBack, scrollIntoViewHandler, useStickyDataAttr } from "@/mainview/scripts/utils"; +import { HandleGoBack, scrollIntoViewHandler, useOnNavigateBack, useStickyDataAttr } from "@/mainview/scripts/utils"; import { FilterUI } from "@/mainview/components/Filters"; import StatList, { StatEntry } from "@/mainview/components/StatList"; import { useIntersectionObserver, useLocalStorage } from "usehooks-ts"; @@ -21,7 +20,7 @@ import Achievements from "@/mainview/components/game/Achievements"; import { GameDetailsContext } from "@/mainview/scripts/contexts"; import { gameQuery, gamesRecommendedBasedOnGameQuery } from "@queries/romm"; import { GamesSection } from "@/mainview/components/store/GamesSection"; -import Details, { DetailElement } from "@/mainview/components/game/Details"; +import Details from "@/mainview/components/game/Details"; import { AutoFocus } from "@/mainview/components/AutoFocus"; export const Route = createFileRoute("/game/$source/$id")({ @@ -31,7 +30,11 @@ export const Route = createFileRoute("/game/$source/$id")({ }, component: RouteComponent, errorComponent: Error, - validateSearch: zodValidator(z.object({ focus: z.string().optional() })) + validateSearch: zodValidator(z.object({ focus: z.string().optional() })), + staticData: { + enterSound: 'openDetails', + goBackSound: "returnDetails" + }, }); function useDetailsSection () @@ -45,10 +48,6 @@ function Error (data: ErrorComponentProps) useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: HandleGoBack }]); const { shortcuts } = useShortcutContext(); - useEffect(() => - { - focusSelf(); - }, []); return
    @@ -68,6 +67,7 @@ function Error (data: ErrorComponentProps)
    + ; } @@ -139,10 +139,10 @@ function Divider (data: { rootFocusKey: string; showShortcuts: boolean; game: Fr function RouteComponent () { + const router = useRouter(); const [recommendedGamesVisible, setRecommendedGamesVisible] = useState(false); const { source, id } = Route.useParams(); const { data } = useQuery(gameQuery(source, id)); - const { focus } = Route.useSearch(); const [, setUpdate] = useState(0); const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "game-details", preferredChildFocusKey: "main-details", forceFocus: true }); const headerRef = useRef(null); @@ -150,7 +150,12 @@ function RouteComponent () const backgroundImage = data ? new URL(`${RPC_URL(__HOST__)}${data.path_cover}`) : undefined; const { data: recommendedGames } = useQuery({ ...gamesRecommendedBasedOnGameQuery(data?.id.source ?? source, data?.id.id ?? id), enabled: !!data && recommendedGamesVisible }); - useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: HandleGoBack }]); + useShortcuts(focusKey, () => [{ + label: "Back", button: GamePadButtonCode.B, action: () => HandleGoBack(router) + }], [router]); + + useOnNavigateBack((s) => s.sound = 'returnDetails'); + const { shortcuts } = useShortcutContext(); useStickyDataAttr(headerRef, sentinelRef, ref); @@ -190,7 +195,7 @@ function RouteComponent () onFocus={scrollIntoViewHandler({ block: 'center' })} onSelect={(id, focus) => { - Router.navigate({ to: '/store/details/emulator/$id', params: { id } }); + router.navigate({ to: '/store/details/emulator/$id', params: { id } }); }} emulators={recommendedEmulators} />} @@ -206,7 +211,7 @@ function RouteComponent ()
    { - Router.navigate({ to: '/game/$source/$id', params: { id: id.id, source: id.source } }); + router.navigate({ to: '/game/$source/$id', params: { id: id.id, source: id.source } }); }} onFocus={scrollIntoViewHandler({ block: 'center', inline: 'nearest' })} games={recommendedGames} />
  • diff --git a/src/mainview/routes/index.tsx b/src/mainview/routes/index.tsx index 945b6d7..d285464 100644 --- a/src/mainview/routes/index.tsx +++ b/src/mainview/routes/index.tsx @@ -14,6 +14,7 @@ import import { createFileRoute, + useRouter, } from "@tanstack/react-router"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import @@ -37,7 +38,6 @@ import Shortcuts from "../components/Shortcuts"; import { PlatformsList } from "../components/PlatformsList"; import { GamePadButtonCode, useShortcutContext, useShortcuts } from "../scripts/shortcuts"; import z from "zod"; -import { Router } from ".."; import CollectionList from "../components/CollectionList"; import { zodValidator } from '@tanstack/zod-adapter'; import { mobileCheck, useDragScroll } from "../scripts/utils"; @@ -45,6 +45,7 @@ import { AnimatedBackgroundContext } from "../scripts/contexts"; import Carousel from "../components/Carousel"; import { closeMutation } from "@queries/system"; import { gameQuery } from "../scripts/queries/romm"; +import { oneShot } from "../scripts/audio/audio"; export const Route = createFileRoute("/")({ component: ConsoleHomeUI, @@ -90,9 +91,10 @@ function HomeListError (data: { focused: boolean; }) function ShowAllGamesCard () { + const router = useRouter(); const handleNavigate = () => { - Router.navigate({ to: '/games', viewTransition: { types: ['zoom-in'] } }); + router.navigate({ to: '/games', viewTransition: { types: ['zoom-in'] } }); }; const { ref } = useFocusable({ focusKey: 'all-games-btn', onEnterPress: handleNavigate }); return
    All Games
    ; @@ -102,6 +104,7 @@ function HomeList (data: { selectedFilter: string; }) { + const router = useRouter(); const queryClient = useQueryClient(); const [initFocus, setInitFocus] = useState(false); const bg = useContext(AnimatedBackgroundContext); @@ -124,7 +127,7 @@ function HomeList (data: { function handleGameSelect (id: FrontEndId, source: string | null, sourceId: string | null) { - Router.navigate({ to: '/game/$source/$id', params: { id: String(sourceId ?? id.id), source: source ?? id.source } }); + router.navigate({ to: '/game/$source/$id', params: { id: String(sourceId ?? id.id), source: source ?? id.source } }); }; let activeList: JSX.Element; @@ -213,9 +216,11 @@ function HomeList (data: { function MainMenu () { + const router = useRouter(); const { ref, focusKey } = useFocusable({ focusKey: `main-menu`, trackChildren: true, + focusBoundaryDirections: ['up', 'down'] }); return ( ; } diff --git a/src/mainview/routes/settings/route.tsx b/src/mainview/routes/settings/route.tsx index c6e8198..5c76442 100644 --- a/src/mainview/routes/settings/route.tsx +++ b/src/mainview/routes/settings/route.tsx @@ -8,6 +8,7 @@ import Outlet, createFileRoute, useMatch, + useRouter, } from "@tanstack/react-router"; import { ViewTransitionOptions } from "@tanstack/router-core"; import classNames from "classnames"; @@ -21,20 +22,24 @@ import MonitorCog, Puzzle, } from "lucide-react"; -import { JSX, useEffect } from "react"; +import { JSX } from "react"; import { twMerge } from "tailwind-merge"; import z from "zod"; import { SettingsSchema } from "../../../shared/constants"; -import { Router } from "../.."; import { GamePadButtonCode, useShortcutContext, useShortcuts } from "@/mainview/scripts/shortcuts"; import Shortcuts from "@/mainview/components/Shortcuts"; import { HandleGoBack } from "@/mainview/scripts/utils"; +import { AutoFocus } from "@/mainview/components/AutoFocus"; +import { oneShot } from "@/mainview/scripts/audio/audio"; export const Route = createFileRoute("/settings")({ component: SettingsUI, validateSearch: z.object({ focus: z.keyof(SettingsSchema).optional() - }) + }), + staticData: { + enterSound: 'openSettings' + } }); function MenuItem (data: { @@ -48,17 +53,18 @@ function MenuItem (data: { label: string; }) { + const router = useRouter(); const acitve = !!useMatch({ from: data.route as any, shouldThrow: false });; const handleNonFocusSelect = () => { if (data.return) { - HandleGoBack(); + HandleGoBack(router); } else if (!acitve) { - Router.navigate({ to: data.route, viewTransition: { types: ['slide-up'] }, replace: true }); + router.navigate({ to: data.route, viewTransition: { types: ['slide-up'] }, replace: true }); } - + oneShot('click'); }; const { ref, focusSelf } = useFocusable({ focusKey: `menu-item-${data.route}`, @@ -67,7 +73,7 @@ function MenuItem (data: { { if (data.focusSelect && !acitve) { - Router.navigate({ to: data.route, viewTransition: { types: ['slide-up'] }, replace: true }); + router.navigate({ to: data.route, viewTransition: { types: ['slide-up'] }, replace: true }); } (ref.current as HTMLElement).scrollIntoView({ inline: 'center' }); }, @@ -81,6 +87,7 @@ function MenuItem (data: {
  • - { - focusSelf(); - }, []); - - useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: HandleGoBack }]); + useShortcuts(focusKey, () => [{ label: "Back", button: GamePadButtonCode.B, action: () => HandleGoBack(router) }], [router]); const { shortcuts } = useShortcutContext(); return ( @@ -196,6 +199,7 @@ export function SettingsUI () + ); } diff --git a/src/mainview/routes/store/details.emulator.$id.tsx b/src/mainview/routes/store/details.emulator.$id.tsx index 1593bd9..9ad4678 100644 --- a/src/mainview/routes/store/details.emulator.$id.tsx +++ b/src/mainview/routes/store/details.emulator.$id.tsx @@ -4,9 +4,8 @@ import useFocusable, FocusContext, } from "@noriginmedia/norigin-spatial-navigation"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, useRouter } from "@tanstack/react-router"; import { GamePadButtonCode, useShortcutContext, useShortcuts } from "@/mainview/scripts/shortcuts"; -import { Router } from "@/mainview"; import Shortcuts from "@/mainview/components/Shortcuts"; import { AnimatedBackground } from "@/mainview/components/AnimatedBackground"; import { systemApi } from "@/mainview/scripts/clientApi"; @@ -18,7 +17,7 @@ import Screenshots from "@/mainview/components/Screenshots"; import { StickyHeaderUI } from "@/mainview/components/Header"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { EmulatorsSection } from "@/mainview/components/store/EmulatorsSection"; -import { HandleGoBack, scrollIntoViewHandler, useJobStatus } from "@/mainview/scripts/utils"; +import { HandleGoBack, scrollIntoViewHandler, useJobStatus, useOnNavigateBack } from "@/mainview/scripts/utils"; import toast from "react-hot-toast"; import { getErrorMessage } from "react-error-boundary"; import { emulatorStatusIcons } from "@/mainview/components/store/StoreEmulatorCard"; @@ -27,6 +26,7 @@ import { GamesSection } from "@/mainview/components/store/GamesSection"; import { deleteBiosMutation, downloadBiosMutation, installEmulatorMutation, storeEmulatorDeleteMutation, storeEmulatorDetailsQuery, storeEmulatorsRecommendedQuery } from "@queries/store"; import { gamesRecommendedBasedOnEmulatorQuery } from "@queries/romm"; import FocusTooltip from "@/mainview/components/FocusTooltip"; +import { AutoFocus } from "@/mainview/components/AutoFocus"; export const Route = createFileRoute('/store/details/emulator/$id')({ component: RouteComponent, @@ -35,6 +35,10 @@ export const Route = createFileRoute('/store/details/emulator/$id')({ ctx.context.queryClient.prefetchQuery(storeEmulatorDetailsQuery(ctx.params.id)); ctx.context.queryClient.prefetchQuery(storeEmulatorsRecommendedQuery(ctx.params.id)); ctx.context.queryClient.prefetchQuery(gamesRecommendedBasedOnEmulatorQuery(ctx.params.id)); + }, + staticData: { + enterSound: "openDetails", + goBackSound: "returnDetails" } }); @@ -288,7 +292,7 @@ function Description (data: { emulator?: FrontEndEmulatorDetailed; }) export function RouteComponent () { const { id } = Route.useParams(); - + const router = useRouter(); const { ref, focusKey, focusSelf } = useFocusable({ focusKey: `GAME_DETAIL_${id}`, trackChildren: true, @@ -301,22 +305,16 @@ export function RouteComponent () useShortcuts(focusKey, () => [{ label: "Return", - action: HandleGoBack, + action: () => HandleGoBack(router), button: GamePadButtonCode.B - }]); + }], [router]); const installMutation = useMutation({ ...installEmulatorMutation(id), onSuccess: (data, variables, onMutateResult, context) => context.client.refetchQueries(storeEmulatorDetailsQuery(id)), }); - useEffect(() => - { - focusSelf(); - }, []); - const { shortcuts } = useShortcutContext(); - const stats: StatEntry[] = []; if (emulator) { @@ -341,6 +339,7 @@ export function RouteComponent () return ( +
    @@ -370,7 +369,7 @@ export function RouteComponent () onFocus={scrollIntoViewHandler({ block: 'center' })} onSelect={(id, focus) => { - Router.navigate({ + router.navigate({ to: '/store/details/emulator/$id', params: { id } }); }} @@ -386,7 +385,7 @@ export function RouteComponent ()
    { - Router.navigate({ + router.navigate({ to: '/game/$source/$id', params: { id: id.id, source: id.source } }); }} games={recommendedGames} />} diff --git a/src/mainview/routes/store/tab/route.tsx b/src/mainview/routes/store/tab/route.tsx index e656b8e..2f8f091 100644 --- a/src/mainview/routes/store/tab/route.tsx +++ b/src/mainview/routes/store/tab/route.tsx @@ -1,4 +1,4 @@ -import { Router } from '@/mainview'; +import { AutoFocus } from '@/mainview/components/AutoFocus'; import { FilterUI } from '@/mainview/components/Filters'; import { HeaderUI } from '@/mainview/components/Header'; import Shortcuts from '@/mainview/components/Shortcuts'; @@ -6,19 +6,21 @@ import { StoreContext } from '@/mainview/scripts/contexts'; import { gameQuery } from '@/mainview/scripts/queries/romm'; import { storeEmulatorDetailsQuery } from '@/mainview/scripts/queries/store'; import { GamePadButtonCode, useShortcutContext, useShortcuts } from '@/mainview/scripts/shortcuts'; -import { mobileCheck, useStickyDataAttr } from '@/mainview/scripts/utils'; +import { HandleGoBack, mobileCheck, useStickyDataAttr } from '@/mainview/scripts/utils'; import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation'; import { useQueryClient } from '@tanstack/react-query'; -import { useMatchRoute } from '@tanstack/react-router'; +import { useMatchRoute, useRouter } from '@tanstack/react-router'; import { createFileRoute, Outlet } from '@tanstack/react-router'; import { zodValidator } from '@tanstack/zod-adapter'; -import { Settings } from 'lucide-react'; -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import z from 'zod'; export const Route = createFileRoute('/store/tab')({ component: RouteComponent, - validateSearch: zodValidator(z.object({ focus: z.string().optional() })) + validateSearch: zodValidator(z.object({ focus: z.string().optional() })), + staticData: { + enterSound: 'openStore' + } }); function useIsSettings (subPath: string) @@ -33,6 +35,7 @@ function useIsSettings (subPath: string) function TopArea (data: { filters: Record; }) { + const router = useRouter(); const { ref, focusKey } = useFocusable({ focusKey: 'top-area', preferredChildFocusKey: `store-tabs`, @@ -44,13 +47,13 @@ function TopArea (data: { filters: Record; }) useShortcuts("STORE_ROOT", () => [{ label: "Return", - action: () => Router.navigate({ to: '/', viewTransition: { types: ['zoom-out'] } }), + action: () => HandleGoBack(router), button: GamePadButtonCode.B - }], []); + }], [router]); const handleNavigate = (s: string) => { - Router.navigate({ to: `/store/tab/${s === 'home' ? '' : s}`, viewTransition: { types: ['slide-up'] }, replace: true }); + router.navigate({ to: `/store/tab/${s === 'home' ? '' : s}`, viewTransition: { types: ['slide-up'] }, replace: true }); }; return
    @@ -76,6 +79,7 @@ function StoreOutlet () function RouteComponent () { // Root spatial nav container + const router = useRouter(); const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "STORE_ROOT", preferredChildFocusKey: 'top-area', @@ -93,25 +97,16 @@ function RouteComponent () const { shortcuts } = useShortcutContext(); const { focus } = Route.useSearch(); - useEffect(() => - { - if (!focus) - { - focusSelf(); - } - }, []); - const handleDetails = (type: string, source: string, id: string, focus: string) => { if (type === 'emulator') { - Router.navigate({ to: '/store/details/emulator/$id', params: { id } }); + router.navigate({ to: '/store/details/emulator/$id', params: { id } }); } else if (type === 'game') { - Router.navigate({ to: '/game/$source/$id', params: { source: source, id: id } }); + router.navigate({ to: '/game/$source/$id', params: { source: source, id: id } }); } - }; const handlePrefetch = (type: string, source: string, id: string) => @@ -150,5 +145,6 @@ function RouteComponent ()
    + ; } diff --git a/src/mainview/scripts/audio/audio.ts b/src/mainview/scripts/audio/audio.ts new file mode 100644 index 0000000..6084ac4 --- /dev/null +++ b/src/mainview/scripts/audio/audio.ts @@ -0,0 +1,70 @@ +import { Howl } from 'howler'; +import sounds from '../../assets/sounds.ogg'; +import soundSprites from '../../assets/sounds.json'; +import { getLocalSetting } from '../utils'; + +const timingMap = new Map(); + +const sound = new Howl({ + src: [sounds], + sprite: soundSprites.sprite as any, + volume: 0.5, +}); +import.meta.hot?.dispose(() => { sound.unload(); }); + +declare module '@tanstack/react-router' { + interface StaticDataRouteOption + { + enterSound?: keyof typeof soundMap | null; + goBackSound?: keyof typeof soundMap | null; + } +} + +const volumeVariation = 0.05; +const rateVariation = 0.01; + +export const soundMap = { + openDetails: { key: 'Classic UI SFX - Chords #1' }, + returnGeneric: { key: 'Classic UI SFX - Short - Low #2' }, + returnDetails: { key: 'Classic UI SFX - Short - Low #5' }, + openGeneric: { key: 'Classic UI SFX - Short - High #9' }, + select: { key: 'Classic UI SFX - Short - High #5', rateVariation, volumeVariation }, + selectAlt: { key: "Classic UI SFX - Short - High #6", rateVariation, volumeVariation }, + selectMenu: { key: 'Classic UI SFX - Short - High #7', rateVariation, volumeVariation }, + selectFilter: { key: 'Classic UI SFX - Short - High #3', volumeVariation }, + closeContext: { key: 'Classic UI SFX - Short - High #19' }, + openContext: { key: 'Classic UI SFX - Short - High #22' }, + openStore: { key: 'Classic UI SFX - Chords #16' }, + openSettings: { key: 'Classic UI SFX - Short - High #8' }, + click: { key: "UI_Single_Set 16_03", rateVariation, volumeVariation }, + clickAlt: { key: "UI_Single_Set 16_01", rateVariation, volumeVariation }, + invalidNavigation: { key: "Classic UI SFX - Short - Low #6", rateVariation, volumeVariation }, +} satisfies Record; + +function sinRanom () +{ + return Math.sin(new Date().getMilliseconds() / 1000 * Math.PI); +} + +function cosRandom () +{ + return Math.sin(new Date().getMilliseconds() / 1000 * Math.PI); +} + +function random () +{ + return Math.random() * 2 - 1; +} + +export function oneShot (id: keyof typeof soundMap) +{ + const currentDate = timingMap.get(id); + if (!getLocalSetting('soundEffects')) return; + if (currentDate && new Date().getTime() - currentDate.getTime() <= 100) return; + const soundValue = soundMap[id] as { key: keyof typeof soundSprites.sprite, rateVariation?: number; volumeVariation?: number; }; + const instanceId = sound.play(soundValue.key); + sound.volume(sound.volume() + random() * (soundValue.volumeVariation ?? 0), instanceId); + sound.rate(1 + random() * (soundValue.rateVariation ?? 0), instanceId); + timingMap.set(id, new Date()); +} + diff --git a/src/mainview/scripts/audio/audioCallbacks.ts b/src/mainview/scripts/audio/audioCallbacks.ts new file mode 100644 index 0000000..4a8f744 --- /dev/null +++ b/src/mainview/scripts/audio/audioCallbacks.ts @@ -0,0 +1,90 @@ +import { Router } from "@/mainview"; +import { oneShot, soundMap } from "./audio"; +export default function load () +{ + let lastLocationPath: string | undefined; + const unsub = Router.history.subscribe((op) => + { + if (op.action.type === 'PUSH') + { + lastLocationPath = op.location.pathname; + + const routes = Router.matchRoutes(op.location.pathname); + const soundRoute = routes.find(r => r.staticData.enterSound !== undefined); + if (soundRoute) + { + if (soundRoute.staticData.enterSound) oneShot(soundRoute.staticData.enterSound); + } else + { + oneShot("openGeneric"); + } + + } else if (op.action.type === 'BACK') + { + if (lastLocationPath) + { + const soundRoutes = Router.matchRoutes(lastLocationPath); + const soundRoute = soundRoutes.find(r => r.staticData.goBackSound !== undefined); + if (soundRoute) + { + if (soundRoute.staticData.goBackSound) oneShot(soundRoute.staticData.goBackSound); + } else + { + oneShot("returnGeneric"); + } + } else + { + oneShot("returnGeneric"); + } + + lastLocationPath = op.location.state.key; + } + }); + + let focusChangeDebounced: undefined | NodeJS.Timeout; + + const focuschangedHandler = (e: CustomEvent) => + { + clearTimeout(focusChangeDebounced); + if (!e.detail.focusKeyChanged) return; + + if (e.detail.nativeEvent || e.detail.event) + { + let sound: keyof typeof soundMap; + if (e.detail.node && e.detail.node.matches('[data-sound-category="menu"]')) + { + sound = 'selectMenu'; + + } else if (e.detail.node && e.detail.node.matches('[data-sound-category="filter"]')) + { + sound = "selectFilter"; + } + else if (e.detail.node && e.detail.node.matches('[data-sound-category="emulator"]')) + { + sound = "selectAlt"; + } + else if (!e.detail.node || !e.detail.node.matches('[data-sound-disable="focus"]')) + { + sound = e.detail.sound as any ?? 'select'; + } + + setTimeout(() => + { + if (e.detail.nativeEvent || e.detail.event) + { + oneShot(sound); + } + }, 10); + } + }; + + window.addEventListener('focuschanged', focuschangedHandler as any); + + return { + cleanup: () => + { + unsub(); + window.removeEventListener('focuschanged', focuschangedHandler as any); + } + }; +} \ No newline at end of file diff --git a/src/mainview/scripts/gamepads.ts b/src/mainview/scripts/gamepads.ts index fed3f3d..e7edf3b 100644 --- a/src/mainview/scripts/gamepads.ts +++ b/src/mainview/scripts/gamepads.ts @@ -2,6 +2,7 @@ import { getCurrentFocusKey, navigateByDirection } from "@noriginmedia/norigin-s import { GetFocusedElement } from "./spatialNavigation"; import { useEffect, useState } from "react"; import { mobileCheck } from "./utils"; +import { oneShot } from "./audio/audio"; let loopStarted = false; let isTouching = false; @@ -104,7 +105,10 @@ function throttleNav (key: string, dir: string, event: Event) const speed = Math.max(maxSpeed - (maxSpeed - minSpeed) * (acceleration / 6), minSpeed); if ((currentDate.getTime() - (lastTime ?? 0) > speed)) { + const currentFocusKey = getCurrentFocusKey(); navigateByDirection(dir, { event }); + if (currentFocusKey === getCurrentFocusKey()) + oneShot('invalidNavigation'); throttleMap.set(key, currentDate.getTime()); throttleAcceleration.set(key, acceleration + 1); return true; diff --git a/src/mainview/scripts/spatialNavigation.ts b/src/mainview/scripts/spatialNavigation.ts index 3d4b182..3129f2a 100644 --- a/src/mainview/scripts/spatialNavigation.ts +++ b/src/mainview/scripts/spatialNavigation.ts @@ -1,6 +1,5 @@ import { - FocusDetails, getCurrentFocusKey, init, SpatialNavigation, @@ -9,7 +8,7 @@ import UseFocusableResult, } from "@noriginmedia/norigin-spatial-navigation"; import { RefObject, useEffect, useState } from "react"; -import { focusQueue, Router } from ".."; +import { focusQueue } from "../App"; init({ shouldFocusDOMNode: false, @@ -97,13 +96,21 @@ SpatialNavigation.updateLayout = (focusKey) => SpatialNavigation.setFocus = (newFocusKey, focusDetails) => { setFocus(newFocusKey, focusDetails); - dispatchFocusedEvent(new CustomEvent('focuschanged', { bubbles: true, detail: focusDetails })); }; SpatialNavigation.setCurrentFocusedKey = (newFocusKey, focusDetails) => { + const details: FocusEventDetails = { + ...focusDetails, + focusKey: newFocusKey, + focusKeyChanged: newFocusKey !== getCurrentFocusKey(), + node: GetFocusedElement(newFocusKey) + }; setCurrentFocusedKey(newFocusKey, focusDetails); - window.dispatchEvent(new CustomEvent('focuschanged', { bubbles: true, detail: focusDetails })); + window.dispatchEvent(new CustomEvent('focuschanged', { + bubbles: true, + detail: details + })); }; SpatialNavigation.updateFocusable = (key, data) => diff --git a/src/mainview/scripts/utils.ts b/src/mainview/scripts/utils.ts index fd2572b..a9666be 100644 --- a/src/mainview/scripts/utils.ts +++ b/src/mainview/scripts/utils.ts @@ -3,7 +3,8 @@ import { RefObject, useEffect, useRef, useState } from "react"; import { useLocalStorage } from "usehooks-ts"; import { jobsApi } from "./clientApi"; import { JobsAPIType } from "@/bun/api/rpc"; -import { Router } from ".."; +import { AnyRouter, Router, useRouter } from "@tanstack/react-router"; +import { soundMap } from "./audio/audio"; export type ScrollSaveParams = { id: string; @@ -59,6 +60,13 @@ export function mobileCheck () return check; }; +export function getLocalSetting (key: TKey) +{ + const localValueRaw = localStorage.getItem(key); + if (!localValueRaw) return LocalSettingsSchema.shape[key].parse(undefined); + return LocalSettingsSchema.shape[key].parse(JSON.parse(localValueRaw)); +} + export function useLocalSetting (key: TKey) { const [localValue] = useLocalStorage(key, LocalSettingsSchema.shape[key].parse(undefined), { deserializer: (value) => LocalSettingsSchema.shape[key].parse(JSON.parse(value)) }); @@ -218,7 +226,7 @@ export function scrollIntoViewHandler (params?: ScrollIntoViewOptions) return (focusKey: string, node: HTMLElement, details: any) => { if (details.nativeEvent instanceof PointerEvent) return; - node.scrollIntoView({ ...params, behavior: details.instant ? 'instant' : 'smooth' }); + node.scrollIntoView({ ...params, behavior: details.instant || !details.event ? 'instant' : 'smooth' }); }; } @@ -315,13 +323,37 @@ export function useJobStatus) => void) +{ + const router = useRouter(); + const prevIndex = useRef(router.history.location.state.__TSR_index); + + useEffect(() => + { + const unsub = router.history.subscribe(() => + { + const currentIndex = router.history.location.state.__TSR_index; + const isBack = currentIndex < prevIndex.current; + + if (isBack) + { + callback(router.history.location.state); + } + + prevIndex.current = currentIndex; + }); + + return unsub; + }, [router]); } \ No newline at end of file diff --git a/src/mainview/types.d.ts b/src/mainview/types.d.ts index 699ca3c..00aca5e 100644 --- a/src/mainview/types.d.ts +++ b/src/mainview/types.d.ts @@ -16,6 +16,25 @@ declare global "save-scroll"?: boolean; } } + + module "@noriginmedia/norigin-spatial-navigation" { + declare interface FocusDetails + { + instant?: boolean; + sound?: string; + } + } +} + +declare interface FocusEventDetails +{ + focusKey: string; + instant?: boolean; + sound?: string; + nativeEvent?: any; + event?: Event; + node: HTMLElement | undefined; + focusKeyChanged: boolean; } declare interface FocusParams diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 66e3a78..acced4c 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -41,7 +41,8 @@ export const SettingsSchema = z.object({ export const LocalSettingsSchema = z.object({ backgroundBlur: z.stringbool().or(z.boolean()).default(true), backgroundAnimation: z.stringbool().or(z.boolean()).default(true), - theme: z.enum(['dark', 'light', 'auto']).default('auto') + theme: z.enum(['dark', 'light', 'auto']).default('auto'), + soundEffects: z.boolean().default(true) }); export const GameListFilterSchema = z.object({ diff --git a/src/sounds/Classic UI SFX - Chords #1.wav b/src/sounds/Classic UI SFX - Chords #1.wav new file mode 100644 index 0000000..eef68fe --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #1.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cce497234f22db3e9d1c0e720c36242b3e0d68a8e5ebed0d99df9dbfb5a7ac85 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #10.wav b/src/sounds/Classic UI SFX - Chords #10.wav new file mode 100644 index 0000000..dff9ef9 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #10.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f5e37d4692690781115bef69f033c371c89fc5e3be3415c01bcfb47f5b03c9f +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #11.wav b/src/sounds/Classic UI SFX - Chords #11.wav new file mode 100644 index 0000000..975f24d --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #11.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61c21f7ec7440719aef486730104cc1110c881de5a38e58151101088b7f63e89 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #12.wav b/src/sounds/Classic UI SFX - Chords #12.wav new file mode 100644 index 0000000..b6db6ee --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #12.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80bbc3cddf2f040a96a989e4febf66885a031a42438e705267ecad60e69caf23 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #13.wav b/src/sounds/Classic UI SFX - Chords #13.wav new file mode 100644 index 0000000..bdd6474 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #13.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:532526fee3cea70f093cceef20d1a2587e334f13c293461c152b9e4faf5c8ab5 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #14.wav b/src/sounds/Classic UI SFX - Chords #14.wav new file mode 100644 index 0000000..86c4cf1 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #14.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca7fadf4951a5beace5c5db4a24fd86f8907d0071a7f9a3bc600e973399b001f +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #15.wav b/src/sounds/Classic UI SFX - Chords #15.wav new file mode 100644 index 0000000..40c31fb --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #15.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54ed638f5ecca2615e0ac4bd42031efa89de810f18b0b3c0b0f2920bfaf021f6 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #16.wav b/src/sounds/Classic UI SFX - Chords #16.wav new file mode 100644 index 0000000..c019363 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #16.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e67390430ce0b19689f5322ad6ff78cd0a7f01cea6b55d02fef19c72ea77a653 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #17.wav b/src/sounds/Classic UI SFX - Chords #17.wav new file mode 100644 index 0000000..e3f84cb --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #17.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90fe98d297a58235bda0a9a4bfc367914f052a550a7289fe652617f30f0b5e32 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #18.wav b/src/sounds/Classic UI SFX - Chords #18.wav new file mode 100644 index 0000000..8883284 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #18.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb19e2996301ccfcfdf328c2f2b553ff9aa0dfe1ff4982ae1c54c9d6a2ba2438 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #19.wav b/src/sounds/Classic UI SFX - Chords #19.wav new file mode 100644 index 0000000..0edb657 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #19.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31630542c8751e4fcdd0a9ab211816f09aea6c4bf6069694e291b18b0774d7df +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #2.wav b/src/sounds/Classic UI SFX - Chords #2.wav new file mode 100644 index 0000000..4b38c6b --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #2.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5dc42fc6845b2ef1ee3db50d81335e93e97902cde85fcf8fe3e5ee29c83163e9 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #20.wav b/src/sounds/Classic UI SFX - Chords #20.wav new file mode 100644 index 0000000..7d61fa7 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #20.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52d3abeb064f3e749d0f4fd29f92b19e54e1477ec80155aca906ae8975e2709f +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #3.wav b/src/sounds/Classic UI SFX - Chords #3.wav new file mode 100644 index 0000000..a9778fb --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #3.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1295f494ec9d295710e7bb1cf467fb118e102891dc9009b86b0ae9960d32e382 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #4.wav b/src/sounds/Classic UI SFX - Chords #4.wav new file mode 100644 index 0000000..b2f258e --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #4.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5e387f33652387eda2189fc9f6c2194b36a9aac272b36ccc64c4e468b77e214 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #5.wav b/src/sounds/Classic UI SFX - Chords #5.wav new file mode 100644 index 0000000..6f2dc11 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #5.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:739c571b6b1ab60a002b9b357878ae91bb9df576d52e5480f07c17886a53f805 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #6.wav b/src/sounds/Classic UI SFX - Chords #6.wav new file mode 100644 index 0000000..fc36385 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #6.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d80b6464291c88b7f33748d4a4e93ed07ee1756d5c623b3862bd3847767d7b54 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #7.wav b/src/sounds/Classic UI SFX - Chords #7.wav new file mode 100644 index 0000000..33f80af --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #7.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:796918fa72911e50c7d762f0fb599427c5924d53b627a1b735f16ca1a9f54fe3 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #8.wav b/src/sounds/Classic UI SFX - Chords #8.wav new file mode 100644 index 0000000..047aa68 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #8.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0439d8fbe9c4734acd7b40e440027d2a7a1c1ae20218f1ca2e3dacd318d776f9 +size 769182 diff --git a/src/sounds/Classic UI SFX - Chords #9.wav b/src/sounds/Classic UI SFX - Chords #9.wav new file mode 100644 index 0000000..7368828 --- /dev/null +++ b/src/sounds/Classic UI SFX - Chords #9.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe8e930484f41092b1518f0f3839b617561f1c62ce4b7532158c0cf484fd4d6f +size 769182 diff --git a/src/sounds/Classic UI SFX - Short - High #1.wav b/src/sounds/Classic UI SFX - Short - High #1.wav new file mode 100644 index 0000000..a29dbce --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #1.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa96866433176c69bf23254badee5abd87741816fe833a07a26d6031020464a2 +size 489182 diff --git a/src/sounds/Classic UI SFX - Short - High #10.wav b/src/sounds/Classic UI SFX - Short - High #10.wav new file mode 100644 index 0000000..8c9c510 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #10.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e58d993037d6c05797c1ecadd738f484d2c1428db6d96381522ef1254b6f794f +size 490182 diff --git a/src/sounds/Classic UI SFX - Short - High #11.wav b/src/sounds/Classic UI SFX - Short - High #11.wav new file mode 100644 index 0000000..558786a --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #11.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d648dc7030de8bc59abc70a0153e37b08cf4267228aab41a321eeaa3f03fb2fc +size 562182 diff --git a/src/sounds/Classic UI SFX - Short - High #12.wav b/src/sounds/Classic UI SFX - Short - High #12.wav new file mode 100644 index 0000000..cbd310c --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #12.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b4c11124a6a9542a3e672e4707d9dd67ea5f805ec239c310218c53d61634d4e +size 562182 diff --git a/src/sounds/Classic UI SFX - Short - High #13.wav b/src/sounds/Classic UI SFX - Short - High #13.wav new file mode 100644 index 0000000..fa8a598 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #13.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5911be904de2655606bd16834391d21f56313d6fce28ed5cfaaf560331a4be80 +size 576182 diff --git a/src/sounds/Classic UI SFX - Short - High #14.wav b/src/sounds/Classic UI SFX - Short - High #14.wav new file mode 100644 index 0000000..e7e21ca --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #14.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:815699bd8283ecd8af0cdfcf384a97a6a61a8aa6ab9400d0fa079267eee0567e +size 538182 diff --git a/src/sounds/Classic UI SFX - Short - High #15.wav b/src/sounds/Classic UI SFX - Short - High #15.wav new file mode 100644 index 0000000..142bc6f --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #15.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95b2b932aade06ea1575755fa516aec836224397145d6b22270096ae74078b7c +size 523182 diff --git a/src/sounds/Classic UI SFX - Short - High #16.wav b/src/sounds/Classic UI SFX - Short - High #16.wav new file mode 100644 index 0000000..d37ab0c --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #16.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8340972a758f262ff71b319a43bcdebb685d3c7036a169276b095b726c22000b +size 562182 diff --git a/src/sounds/Classic UI SFX - Short - High #17.wav b/src/sounds/Classic UI SFX - Short - High #17.wav new file mode 100644 index 0000000..112b03e --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #17.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fbe6ca64226581e8bafa0891fabfd465d9045b395c4a21534a04af435342971 +size 553182 diff --git a/src/sounds/Classic UI SFX - Short - High #18.wav b/src/sounds/Classic UI SFX - Short - High #18.wav new file mode 100644 index 0000000..34ef574 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #18.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95539682288858aaf0b37bc35715b71060e76bf15b81db3898a0f739f062b243 +size 453182 diff --git a/src/sounds/Classic UI SFX - Short - High #19.wav b/src/sounds/Classic UI SFX - Short - High #19.wav new file mode 100644 index 0000000..b165ece --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #19.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d312f707829b975fb625bed15879118479a7927d9e29bd62d2c1c716d35b367f +size 586182 diff --git a/src/sounds/Classic UI SFX - Short - High #2.wav b/src/sounds/Classic UI SFX - Short - High #2.wav new file mode 100644 index 0000000..f6d5fa2 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #2.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89c235075af8db515f97c2bfd50f5350c28c2ba2079e6fad8fdfb963f24a12a1 +size 546182 diff --git a/src/sounds/Classic UI SFX - Short - High #20.wav b/src/sounds/Classic UI SFX - Short - High #20.wav new file mode 100644 index 0000000..7e23a1e --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #20.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61a585819286d0f11314813ee89bc8f121ca4b362d1721e2edfe14172a6358e7 +size 387182 diff --git a/src/sounds/Classic UI SFX - Short - High #21.wav b/src/sounds/Classic UI SFX - Short - High #21.wav new file mode 100644 index 0000000..49cb131 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #21.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:480c9e31c1a033c959888bd2a2691c772ef80bbec1c5f8dc3fccb7e86da6c153 +size 385182 diff --git a/src/sounds/Classic UI SFX - Short - High #22.wav b/src/sounds/Classic UI SFX - Short - High #22.wav new file mode 100644 index 0000000..acced97 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #22.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ab262921ec2763a84c25d3ec00e1ade68e96393fbd475c28659aa661af9d41f +size 478182 diff --git a/src/sounds/Classic UI SFX - Short - High #23.wav b/src/sounds/Classic UI SFX - Short - High #23.wav new file mode 100644 index 0000000..4d106a7 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #23.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0f09d67685a7cf0b82b66f082ad77cf0b536557306fbf6862ba3ef0b92b8280 +size 472182 diff --git a/src/sounds/Classic UI SFX - Short - High #24.wav b/src/sounds/Classic UI SFX - Short - High #24.wav new file mode 100644 index 0000000..e3d0dc8 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #24.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd3520550af14b8d19b275bba18fb7f2610f156e6eea780837748a068d6a858d +size 402182 diff --git a/src/sounds/Classic UI SFX - Short - High #25.wav b/src/sounds/Classic UI SFX - Short - High #25.wav new file mode 100644 index 0000000..6632b69 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #25.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f18ca1455d31a7c430572c64b85c0c600a0c77986e6411a0f9cff783445393c3 +size 385182 diff --git a/src/sounds/Classic UI SFX - Short - High #3.wav b/src/sounds/Classic UI SFX - Short - High #3.wav new file mode 100644 index 0000000..767c819 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #3.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:140b58fa531bb20dcb284e8babea35ef55fa5d58aff16e8d98b505ccf02115e5 +size 550182 diff --git a/src/sounds/Classic UI SFX - Short - High #4.wav b/src/sounds/Classic UI SFX - Short - High #4.wav new file mode 100644 index 0000000..5eb2d78 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #4.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d11499b350e8e95990d298dc96ce01026973ccf5bd58891c9b505d3ba32b1a82 +size 582182 diff --git a/src/sounds/Classic UI SFX - Short - High #5.wav b/src/sounds/Classic UI SFX - Short - High #5.wav new file mode 100644 index 0000000..8cc1019 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #5.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e92d3e60c23ea4927444a28174a53fcb668d83f850df2494f53d5870613143a +size 499182 diff --git a/src/sounds/Classic UI SFX - Short - High #6.wav b/src/sounds/Classic UI SFX - Short - High #6.wav new file mode 100644 index 0000000..3e496cb --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #6.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ec68d1b1549fed1bccaf62ea4fd80c5c59b9b50750fa324b858e1c370344270 +size 466182 diff --git a/src/sounds/Classic UI SFX - Short - High #7.wav b/src/sounds/Classic UI SFX - Short - High #7.wav new file mode 100644 index 0000000..8fae195 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #7.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14f2dd1c42c354343f80c631721c8dceadb80b8e03ecec4e93294c8ffb21cfcf +size 474182 diff --git a/src/sounds/Classic UI SFX - Short - High #8.wav b/src/sounds/Classic UI SFX - Short - High #8.wav new file mode 100644 index 0000000..a25618d --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #8.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:683fc09fbf580efa6990504bd17861f48495555ac81f4ed2b9e85f14628348fe +size 560182 diff --git a/src/sounds/Classic UI SFX - Short - High #9.wav b/src/sounds/Classic UI SFX - Short - High #9.wav new file mode 100644 index 0000000..9ee3b5f --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - High #9.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b60cd47d7591fc5e2cf49c8139781479d3833d621bba129903d78843d7929380 +size 432182 diff --git a/src/sounds/Classic UI SFX - Short - Low #1.wav b/src/sounds/Classic UI SFX - Short - Low #1.wav new file mode 100644 index 0000000..dba2e40 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #1.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:419e970729e384fcba3a9c4bb3c55cec906dfd20f969c1cf0cbf2ffcbc00638e +size 386182 diff --git a/src/sounds/Classic UI SFX - Short - Low #10.wav b/src/sounds/Classic UI SFX - Short - Low #10.wav new file mode 100644 index 0000000..4eebb5b --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #10.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9749c07cacebb0bfe9924bbf8f0142630c96c0d4d5d0e727f5daeb354f9505a8 +size 580182 diff --git a/src/sounds/Classic UI SFX - Short - Low #11.wav b/src/sounds/Classic UI SFX - Short - Low #11.wav new file mode 100644 index 0000000..d4cbb0d --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #11.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4b7c87005eae8d0b0d329329135a67cd318a775cf2c75dd5ae68974537f9b7c +size 472182 diff --git a/src/sounds/Classic UI SFX - Short - Low #12.wav b/src/sounds/Classic UI SFX - Short - Low #12.wav new file mode 100644 index 0000000..65b3517 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #12.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96018d0ee84d1e27e0fc655c03d4afd3fa0839aecca09fe7ef2f0f104f424e60 +size 557182 diff --git a/src/sounds/Classic UI SFX - Short - Low #13.wav b/src/sounds/Classic UI SFX - Short - Low #13.wav new file mode 100644 index 0000000..ac2b8bf --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #13.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a3de21306c039abc81ace6a9e2e18f581c4993c532c8120126df055be7c9767 +size 546182 diff --git a/src/sounds/Classic UI SFX - Short - Low #14.wav b/src/sounds/Classic UI SFX - Short - Low #14.wav new file mode 100644 index 0000000..ce57f07 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #14.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:848195440e0a64566d175a62aa13685872fdb012bf491e5742984d5059524f5f +size 602182 diff --git a/src/sounds/Classic UI SFX - Short - Low #15.wav b/src/sounds/Classic UI SFX - Short - Low #15.wav new file mode 100644 index 0000000..e8218d5 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #15.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aee89025f7fb8517deb610d2814b941136fda09ebcbbacc78cddfe834c5b348a +size 519182 diff --git a/src/sounds/Classic UI SFX - Short - Low #16.wav b/src/sounds/Classic UI SFX - Short - Low #16.wav new file mode 100644 index 0000000..187b096 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #16.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a0bafde7f6a2b75589faac13b7d2b929239189ebc554c338fd2741e3ea46e78 +size 552182 diff --git a/src/sounds/Classic UI SFX - Short - Low #17.wav b/src/sounds/Classic UI SFX - Short - Low #17.wav new file mode 100644 index 0000000..4a4608d --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #17.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b2daee3db271eb4244dd40ea3abb5a7f036e69e637222f7a611c89a5f0a9a3e +size 562182 diff --git a/src/sounds/Classic UI SFX - Short - Low #18.wav b/src/sounds/Classic UI SFX - Short - Low #18.wav new file mode 100644 index 0000000..efbf4cc --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #18.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bda2874577cb666819141de8eba8375bddcb07bf1e813d33ad718f6828227f1 +size 587182 diff --git a/src/sounds/Classic UI SFX - Short - Low #19.wav b/src/sounds/Classic UI SFX - Short - Low #19.wav new file mode 100644 index 0000000..6efa7e3 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #19.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f577ca8f4b9d172c1e8d10553451128ea424a0cd4e8fc2ba217f1703de74eedc +size 475182 diff --git a/src/sounds/Classic UI SFX - Short - Low #2.wav b/src/sounds/Classic UI SFX - Short - Low #2.wav new file mode 100644 index 0000000..07e9d9a --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #2.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36527b664e371bc952fa2ef6cd6f5479fcfd183144812091caf234be45dd8d3f +size 496182 diff --git a/src/sounds/Classic UI SFX - Short - Low #20.wav b/src/sounds/Classic UI SFX - Short - Low #20.wav new file mode 100644 index 0000000..c8d717c --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #20.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:598800de963759dbe0bca69f66facbe31546c0e39f76f0fe59950ac3dd8f1c08 +size 483182 diff --git a/src/sounds/Classic UI SFX - Short - Low #21.wav b/src/sounds/Classic UI SFX - Short - Low #21.wav new file mode 100644 index 0000000..ea90758 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #21.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca62b2181585cbc11f1c66ba1e1d83b2987e50f9a5adb236c9c80dc29a43675b +size 500182 diff --git a/src/sounds/Classic UI SFX - Short - Low #22.wav b/src/sounds/Classic UI SFX - Short - Low #22.wav new file mode 100644 index 0000000..3505329 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #22.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49ad78c33edc1c0bcfdb5d9d2d6d559b7530403cec0d7ccfb770fb4fb3356527 +size 582182 diff --git a/src/sounds/Classic UI SFX - Short - Low #23.wav b/src/sounds/Classic UI SFX - Short - Low #23.wav new file mode 100644 index 0000000..5cac1d4 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #23.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3c1f824ac3a97213b56c1493b4493ef11d3292bff84eac4eeb9134b9546941c +size 564182 diff --git a/src/sounds/Classic UI SFX - Short - Low #24.wav b/src/sounds/Classic UI SFX - Short - Low #24.wav new file mode 100644 index 0000000..d3b0245 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #24.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb1948113b38e53a46ca720b3096a6b991c4dce76f9b3dbdec2268566587242b +size 501182 diff --git a/src/sounds/Classic UI SFX - Short - Low #25.wav b/src/sounds/Classic UI SFX - Short - Low #25.wav new file mode 100644 index 0000000..aa73af8 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #25.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44dc28db05d46c9eba8710a3f9fade5722551f7bbbd4318f391a78cfc2995538 +size 504182 diff --git a/src/sounds/Classic UI SFX - Short - Low #3.wav b/src/sounds/Classic UI SFX - Short - Low #3.wav new file mode 100644 index 0000000..9d62309 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #3.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b78146a7c13a9dc54279b0d6909ce7d33bd8521f53d8f70d5cc35941b04be055 +size 543182 diff --git a/src/sounds/Classic UI SFX - Short - Low #4.wav b/src/sounds/Classic UI SFX - Short - Low #4.wav new file mode 100644 index 0000000..42499ed --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #4.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfcb2172fc599b189deecc57f0ceaa8fa54059442194829e5ae5ef78bb4960a5 +size 502182 diff --git a/src/sounds/Classic UI SFX - Short - Low #5.wav b/src/sounds/Classic UI SFX - Short - Low #5.wav new file mode 100644 index 0000000..6092153 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #5.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d120f6502806f3b7c1259d89aa90a0cf851f7f4826d454bd34b740f40b73f59 +size 607182 diff --git a/src/sounds/Classic UI SFX - Short - Low #6.wav b/src/sounds/Classic UI SFX - Short - Low #6.wav new file mode 100644 index 0000000..8708e29 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #6.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70ba897bce8a0ec2ce6c0c2b16dfb2fd1b5cefa3eb00c152a489de35188bbf83 +size 448182 diff --git a/src/sounds/Classic UI SFX - Short - Low #7.wav b/src/sounds/Classic UI SFX - Short - Low #7.wav new file mode 100644 index 0000000..64a7cce --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #7.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d465d28fc3c3c9a1dc2992724b3a674ff4d31aa9721c222cd00434ba53f4086 +size 487182 diff --git a/src/sounds/Classic UI SFX - Short - Low #8.wav b/src/sounds/Classic UI SFX - Short - Low #8.wav new file mode 100644 index 0000000..0ed12c7 --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #8.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62dc712e485e785716d944f0764c0588f00dd64185e4a205393f7a7df651ccfb +size 505182 diff --git a/src/sounds/Classic UI SFX - Short - Low #9.wav b/src/sounds/Classic UI SFX - Short - Low #9.wav new file mode 100644 index 0000000..18c0b1a --- /dev/null +++ b/src/sounds/Classic UI SFX - Short - Low #9.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2b0db41db5cddc7e8d6884c856b86f0b77e771958a1da9867455a6212407074 +size 518182 diff --git a/src/sounds/UI_Single_Set 16_01.ogg b/src/sounds/UI_Single_Set 16_01.ogg new file mode 100644 index 0000000..b6c70e0 --- /dev/null +++ b/src/sounds/UI_Single_Set 16_01.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d04ded8bae2d120e79e7969910c7016dc00ec8b22680ab4ed5842d257616c348 +size 6983 diff --git a/src/sounds/UI_Single_Set 16_02.ogg b/src/sounds/UI_Single_Set 16_02.ogg new file mode 100644 index 0000000..1cdb93a --- /dev/null +++ b/src/sounds/UI_Single_Set 16_02.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dee515a756b38ea169ebfabd91320a5d50873993765095920ec2f9e142dfd00d +size 7069 diff --git a/src/sounds/UI_Single_Set 16_03.ogg b/src/sounds/UI_Single_Set 16_03.ogg new file mode 100644 index 0000000..4bdfd18 --- /dev/null +++ b/src/sounds/UI_Single_Set 16_03.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8d972d72b3570f0638e094e84791ad0aa8a39b9d4806b8a5b441802a73bf3ac +size 7156 diff --git a/src/sounds/UI_TwoNote_Set 15_01.ogg b/src/sounds/UI_TwoNote_Set 15_01.ogg new file mode 100644 index 0000000..7d8a3c2 --- /dev/null +++ b/src/sounds/UI_TwoNote_Set 15_01.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad955c552cc1a111a9885fcb09f460e03f6b31ef9ecd1637a49970c0edc5fef2 +size 7624 diff --git a/src/sounds/UI_TwoNote_Set 15_02.ogg b/src/sounds/UI_TwoNote_Set 15_02.ogg new file mode 100644 index 0000000..6a5a4c9 --- /dev/null +++ b/src/sounds/UI_TwoNote_Set 15_02.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78e1b6c3fcff1f9f4a3a2247b8b835d26585b089ea36f21ba212d99ea6f60ba2 +size 7596