Compare commits
14 Commits
5e9acffa09
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5747e3611d | ||
|
|
8a11ee4c29 | ||
|
|
a9ed3dea5f | ||
|
|
5c08238dbd | ||
|
|
e5b3b97680 | ||
|
|
31d7eaf6ac | ||
|
|
9bb32c1756 | ||
|
|
de711a407f | ||
|
|
082b9bb714 | ||
|
|
3c646bc5ac | ||
|
|
2c072f1474 | ||
|
|
9e035d5007 | ||
|
|
2cc51e62f4 | ||
|
|
e3088b7c47 |
@@ -27,6 +27,7 @@
|
|||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"eruda": "^3.4.3",
|
"eruda": "^3.4.3",
|
||||||
|
"howler": "^2.2.4",
|
||||||
"i18next": "^25.8.17",
|
"i18next": "^25.8.17",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"motion": "^12.35.1",
|
"motion": "^12.35.1",
|
||||||
@@ -34,10 +35,12 @@
|
|||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"react-i18next": "^16.5.6",
|
"react-i18next": "^16.5.6",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"xstate": "^5.28.0"
|
"xstate": "^5.28.0",
|
||||||
|
"zustand": "^5.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/router-plugin": "^1.166.3",
|
"@tanstack/router-plugin": "^1.166.3",
|
||||||
|
"@types/howler": "^2.2.12",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
|||||||
115
pnpm-lock.yaml
generated
115
pnpm-lock.yaml
generated
@@ -10,10 +10,10 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))
|
version: 4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
'@tanstack/devtools-vite':
|
'@tanstack/devtools-vite':
|
||||||
specifier: ^0.5.3
|
specifier: ^0.5.3
|
||||||
version: 0.5.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))
|
version: 0.5.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
'@tanstack/react-devtools':
|
'@tanstack/react-devtools':
|
||||||
specifier: ^0.9.10
|
specifier: ^0.9.10
|
||||||
version: 0.9.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)
|
version: 0.9.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)
|
||||||
@@ -47,6 +47,9 @@ importers:
|
|||||||
eruda:
|
eruda:
|
||||||
specifier: ^3.4.3
|
specifier: ^3.4.3
|
||||||
version: 3.4.3
|
version: 3.4.3
|
||||||
|
howler:
|
||||||
|
specifier: ^2.2.4
|
||||||
|
version: 2.2.4
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^25.8.17
|
specifier: ^25.8.17
|
||||||
version: 25.8.17(typescript@5.9.3)
|
version: 25.8.17(typescript@5.9.3)
|
||||||
@@ -71,10 +74,16 @@ importers:
|
|||||||
xstate:
|
xstate:
|
||||||
specifier: ^5.28.0
|
specifier: ^5.28.0
|
||||||
version: 5.28.0
|
version: 5.28.0
|
||||||
|
zustand:
|
||||||
|
specifier: ^5.0.12
|
||||||
|
version: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tanstack/router-plugin':
|
'@tanstack/router-plugin':
|
||||||
specifier: ^1.166.3
|
specifier: ^1.166.3
|
||||||
version: 1.166.3(@tanstack/react-router@1.166.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))
|
version: 1.166.3(@tanstack/react-router@1.166.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
|
'@types/howler':
|
||||||
|
specifier: ^2.2.12
|
||||||
|
version: 2.2.12
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^24.10.1
|
specifier: ^24.10.1
|
||||||
version: 24.12.0
|
version: 24.12.0
|
||||||
@@ -86,7 +95,7 @@ importers:
|
|||||||
version: 19.2.3(@types/react@19.2.14)
|
version: 19.2.3(@types/react@19.2.14)
|
||||||
'@vitejs/plugin-react-swc':
|
'@vitejs/plugin-react-swc':
|
||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))
|
version: 4.2.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||||
globals:
|
globals:
|
||||||
specifier: ^17.4.0
|
specifier: ^17.4.0
|
||||||
version: 17.4.0
|
version: 17.4.0
|
||||||
@@ -110,7 +119,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
vite:
|
vite:
|
||||||
specifier: ^7.3.1
|
specifier: ^7.3.1
|
||||||
version: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2)
|
version: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -376,6 +385,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@jridgewell/source-map@0.3.11':
|
||||||
|
resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.5':
|
'@jridgewell/sourcemap-codec@1.5.5':
|
||||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||||
|
|
||||||
@@ -1223,6 +1235,9 @@ packages:
|
|||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
'@types/howler@2.2.12':
|
||||||
|
resolution: {integrity: sha512-hy769UICzOSdK0Kn1FBk4gN+lswcj1EKRkmiDtMkUGvFfYJzgaDXmVXkSShS2m89ERAatGIPnTUlp2HhfkVo5g==}
|
||||||
|
|
||||||
'@types/node@24.12.0':
|
'@types/node@24.12.0':
|
||||||
resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==}
|
resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==}
|
||||||
|
|
||||||
@@ -1314,6 +1329,9 @@ packages:
|
|||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
buffer-from@1.1.2:
|
||||||
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1359,6 +1377,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
|
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
commander@2.20.3:
|
||||||
|
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
@@ -1558,6 +1579,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
howler@2.2.4:
|
||||||
|
resolution: {integrity: sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==}
|
||||||
|
|
||||||
html-parse-stringify@3.0.1:
|
html-parse-stringify@3.0.1:
|
||||||
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||||
|
|
||||||
@@ -2049,6 +2073,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
source-map-support@0.5.21:
|
||||||
|
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||||
|
|
||||||
source-map@0.6.1:
|
source-map@0.6.1:
|
||||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -2092,6 +2119,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
terser@5.46.1:
|
||||||
|
resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
tiny-invariant@1.3.3:
|
tiny-invariant@1.3.3:
|
||||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||||
|
|
||||||
@@ -2251,6 +2283,24 @@ packages:
|
|||||||
zod@3.25.76:
|
zod@3.25.76:
|
||||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||||
|
|
||||||
|
zustand@5.0.12:
|
||||||
|
resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==}
|
||||||
|
engines: {node: '>=12.20.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=18.0.0'
|
||||||
|
immer: '>=9.0.6'
|
||||||
|
react: '>=18.0.0'
|
||||||
|
use-sync-external-store: '>=1.2.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
use-sync-external-store:
|
||||||
|
optional: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@ark/schema@0.56.0':
|
'@ark/schema@0.56.0':
|
||||||
@@ -2466,6 +2516,12 @@ snapshots:
|
|||||||
|
|
||||||
'@jridgewell/resolve-uri@3.1.2': {}
|
'@jridgewell/resolve-uri@3.1.2': {}
|
||||||
|
|
||||||
|
'@jridgewell/source-map@0.3.11':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/gen-mapping': 0.3.13
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
|
|
||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
@@ -2872,12 +2928,12 @@ snapshots:
|
|||||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
|
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
|
||||||
'@tailwindcss/oxide-win32-x64-msvc': 4.2.1
|
'@tailwindcss/oxide-win32-x64-msvc': 4.2.1
|
||||||
|
|
||||||
'@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))':
|
'@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tailwindcss/node': 4.2.1
|
'@tailwindcss/node': 4.2.1
|
||||||
'@tailwindcss/oxide': 4.2.1
|
'@tailwindcss/oxide': 4.2.1
|
||||||
tailwindcss: 4.2.1
|
tailwindcss: 4.2.1
|
||||||
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
'@tanstack/devtools-client@0.0.6':
|
'@tanstack/devtools-client@0.0.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2901,7 +2957,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- csstype
|
- csstype
|
||||||
|
|
||||||
'@tanstack/devtools-vite@0.5.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))':
|
'@tanstack/devtools-vite@0.5.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
'@babel/generator': 7.29.1
|
'@babel/generator': 7.29.1
|
||||||
@@ -2913,7 +2969,7 @@ snapshots:
|
|||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
launch-editor: 2.13.1
|
launch-editor: 2.13.1
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -3026,7 +3082,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@tanstack/router-plugin@1.166.3(@tanstack/react-router@1.166.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))':
|
'@tanstack/router-plugin@1.166.3(@tanstack/react-router@1.166.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
|
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
|
||||||
@@ -3043,7 +3099,7 @@ snapshots:
|
|||||||
zod: 3.25.76
|
zod: 3.25.76
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tanstack/react-router': 1.166.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
'@tanstack/react-router': 1.166.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -3122,6 +3178,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
'@types/howler@2.2.12': {}
|
||||||
|
|
||||||
'@types/node@24.12.0':
|
'@types/node@24.12.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
@@ -3134,11 +3192,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
|
|
||||||
'@vitejs/plugin-react-swc@4.2.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))':
|
'@vitejs/plugin-react-swc@4.2.3(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rolldown/pluginutils': 1.0.0-rc.2
|
'@rolldown/pluginutils': 1.0.0-rc.2
|
||||||
'@swc/core': 1.15.18
|
'@swc/core': 1.15.18
|
||||||
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2)
|
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@swc/helpers'
|
- '@swc/helpers'
|
||||||
|
|
||||||
@@ -3222,6 +3280,9 @@ snapshots:
|
|||||||
node-releases: 2.0.36
|
node-releases: 2.0.36
|
||||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||||
|
|
||||||
|
buffer-from@1.1.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
call-bind-apply-helpers@1.0.2:
|
call-bind-apply-helpers@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
@@ -3270,6 +3331,9 @@ snapshots:
|
|||||||
|
|
||||||
commander@14.0.3: {}
|
commander@14.0.3: {}
|
||||||
|
|
||||||
|
commander@2.20.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cookie-es@2.0.0: {}
|
cookie-es@2.0.0: {}
|
||||||
@@ -3452,6 +3516,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
howler@2.2.4: {}
|
||||||
|
|
||||||
html-parse-stringify@3.0.1:
|
html-parse-stringify@3.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
void-elements: 3.1.0
|
void-elements: 3.1.0
|
||||||
@@ -3908,6 +3974,12 @@ snapshots:
|
|||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
|
source-map-support@0.5.21:
|
||||||
|
dependencies:
|
||||||
|
buffer-from: 1.1.2
|
||||||
|
source-map: 0.6.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
source-map@0.6.1: {}
|
source-map@0.6.1: {}
|
||||||
|
|
||||||
source-map@0.7.6: {}
|
source-map@0.7.6: {}
|
||||||
@@ -3946,6 +4018,14 @@ snapshots:
|
|||||||
|
|
||||||
tapable@2.3.0: {}
|
tapable@2.3.0: {}
|
||||||
|
|
||||||
|
terser@5.46.1:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/source-map': 0.3.11
|
||||||
|
acorn: 8.16.0
|
||||||
|
commander: 2.20.3
|
||||||
|
source-map-support: 0.5.21
|
||||||
|
optional: true
|
||||||
|
|
||||||
tiny-invariant@1.3.3: {}
|
tiny-invariant@1.3.3: {}
|
||||||
|
|
||||||
tiny-warning@1.0.3: {}
|
tiny-warning@1.0.3: {}
|
||||||
@@ -4008,7 +4088,7 @@ snapshots:
|
|||||||
varint@6.0.0:
|
varint@6.0.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2):
|
vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.27.3
|
esbuild: 0.27.3
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@@ -4023,6 +4103,7 @@ snapshots:
|
|||||||
lightningcss: 1.31.1
|
lightningcss: 1.31.1
|
||||||
sass: 1.97.3
|
sass: 1.97.3
|
||||||
sass-embedded: 1.97.3
|
sass-embedded: 1.97.3
|
||||||
|
terser: 5.46.1
|
||||||
tsx: 4.21.0
|
tsx: 4.21.0
|
||||||
yaml: 2.8.2
|
yaml: 2.8.2
|
||||||
|
|
||||||
@@ -4052,3 +4133,9 @@ snapshots:
|
|||||||
yaml@2.8.2: {}
|
yaml@2.8.2: {}
|
||||||
|
|
||||||
zod@3.25.76: {}
|
zod@3.25.76: {}
|
||||||
|
|
||||||
|
zustand@5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)):
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
react: 19.2.4
|
||||||
|
use-sync-external-store: 1.6.0(react@19.2.4)
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "aus",
|
"off": "aus",
|
||||||
"on": "an"
|
"on": "an"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Antwort",
|
||||||
|
"1q": "Frage",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Bienenhaus",
|
"apiary": "Bienenhaus",
|
||||||
"cashdesk": "Kasse",
|
"cashdesk": "Kasse",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "von"
|
"of": "von"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Support kontaktieren",
|
|
||||||
"text": "Wenn Sie Fragen zum Spiel haben, wenden Sie sich bitte an unseren Support."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Kontoinformationen",
|
"accountInfo": "Kontoinformationen",
|
||||||
"back": "Zurück",
|
"back": "Zurück",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Support",
|
"support": "Support",
|
||||||
"transactionHistory": "Transaktionsverlauf"
|
"transactionHistory": "Transaktionsverlauf"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Support kontaktieren",
|
||||||
|
"text": "Wenn Sie Fragen zum Spiel haben, wenden Sie sich bitte an unseren Support.",
|
||||||
|
"title": "Support"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"operationType": "Vorgangsart",
|
"operationType": "Vorgangsart",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "off",
|
"off": "off",
|
||||||
"on": "on"
|
"on": "on"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Answer",
|
||||||
|
"1q": "Question",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Apiary",
|
"apiary": "Apiary",
|
||||||
"cashdesk": "Cashdesk",
|
"cashdesk": "Cashdesk",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "of"
|
"of": "of"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Contact support",
|
|
||||||
"text": "If you have any questions related to the game, please contact our support team."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Account information",
|
"accountInfo": "Account information",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Support",
|
"support": "Support",
|
||||||
"transactionHistory": "Transaction History"
|
"transactionHistory": "Transaction History"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Contact support",
|
||||||
|
"text": "If you have any questions related to the game, please contact our support team.",
|
||||||
|
"title": "Support"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"operationType": "Operation type",
|
"operationType": "Operation type",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "no",
|
"off": "no",
|
||||||
"on": "sí"
|
"on": "sí"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Respuesta",
|
||||||
|
"1q": "Pregunta",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Apiario",
|
"apiary": "Apiario",
|
||||||
"cashdesk": "Caja",
|
"cashdesk": "Caja",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "de"
|
"of": "de"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Contactar soporte",
|
|
||||||
"text": "Si tienes alguna pregunta relacionada con el juego, contacta con nuestro equipo de soporte."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Información de la cuenta",
|
"accountInfo": "Información de la cuenta",
|
||||||
"back": "Volver",
|
"back": "Volver",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Soporte",
|
"support": "Soporte",
|
||||||
"transactionHistory": "Historial de transacciones"
|
"transactionHistory": "Historial de transacciones"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Contactar soporte",
|
||||||
|
"text": "Si tienes alguna pregunta relacionada con el juego, contacta con nuestro equipo de soporte.",
|
||||||
|
"title": "Soporte"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Fecha",
|
"date": "Fecha",
|
||||||
"operationType": "Tipo de operación",
|
"operationType": "Tipo de operación",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "non",
|
"off": "non",
|
||||||
"on": "oui"
|
"on": "oui"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Réponse",
|
||||||
|
"1q": "Question",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Rucher",
|
"apiary": "Rucher",
|
||||||
"cashdesk": "Caisse",
|
"cashdesk": "Caisse",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "sur"
|
"of": "sur"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Contacter le support",
|
|
||||||
"text": "Si vous avez des questions liées au jeu, veuillez contacter notre équipe de support."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Informations du compte",
|
"accountInfo": "Informations du compte",
|
||||||
"back": "Retour",
|
"back": "Retour",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Support",
|
"support": "Support",
|
||||||
"transactionHistory": "Historique des transactions"
|
"transactionHistory": "Historique des transactions"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Contacter le support",
|
||||||
|
"text": "Si vous avez des questions liées au jeu, veuillez contacter notre équipe de support.",
|
||||||
|
"title": "Support"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"operationType": "Type d'opération",
|
"operationType": "Type d'opération",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "mati",
|
"off": "mati",
|
||||||
"on": "nyala"
|
"on": "nyala"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Jawaban",
|
||||||
|
"1q": "Pertanyaan",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Peternakan Lebah",
|
"apiary": "Peternakan Lebah",
|
||||||
"cashdesk": "Kasir",
|
"cashdesk": "Kasir",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "dari"
|
"of": "dari"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Hubungi dukungan",
|
|
||||||
"text": "Jika Anda memiliki pertanyaan terkait permainan, silakan hubungi tim dukungan kami."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Informasi akun",
|
"accountInfo": "Informasi akun",
|
||||||
"back": "Kembali",
|
"back": "Kembali",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Dukungan",
|
"support": "Dukungan",
|
||||||
"transactionHistory": "Riwayat transaksi"
|
"transactionHistory": "Riwayat transaksi"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Hubungi dukungan",
|
||||||
|
"text": "Jika Anda memiliki pertanyaan terkait permainan, silakan hubungi tim dukungan kami.",
|
||||||
|
"title": "Dukungan"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Tanggal",
|
"date": "Tanggal",
|
||||||
"operationType": "Jenis operasi",
|
"operationType": "Jenis operasi",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "no",
|
"off": "no",
|
||||||
"on": "sì"
|
"on": "sì"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Risposta",
|
||||||
|
"1q": "Domanda",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Apiario",
|
"apiary": "Apiario",
|
||||||
"cashdesk": "Cassa",
|
"cashdesk": "Cassa",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "di"
|
"of": "di"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Contatta il supporto",
|
|
||||||
"text": "Se hai domande relative al gioco, contatta il nostro team di supporto."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Informazioni account",
|
"accountInfo": "Informazioni account",
|
||||||
"back": "Indietro",
|
"back": "Indietro",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Supporto",
|
"support": "Supporto",
|
||||||
"transactionHistory": "Cronologia transazioni"
|
"transactionHistory": "Cronologia transazioni"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Contatta il supporto",
|
||||||
|
"text": "Se hai domande relative al gioco, contatta il nostro team di supporto.",
|
||||||
|
"title": "Supporto"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Data",
|
"date": "Data",
|
||||||
"operationType": "Tipo di operazione",
|
"operationType": "Tipo di operazione",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "uit",
|
"off": "uit",
|
||||||
"on": "aan"
|
"on": "aan"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Antwoord",
|
||||||
|
"1q": "Vraag",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Bijenstal",
|
"apiary": "Bijenstal",
|
||||||
"cashdesk": "Kassa",
|
"cashdesk": "Kassa",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "van"
|
"of": "van"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Contact opnemen",
|
|
||||||
"text": "Als u vragen heeft over het spel, neem dan contact op met ons ondersteuningsteam."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Accountinformatie",
|
"accountInfo": "Accountinformatie",
|
||||||
"back": "Terug",
|
"back": "Terug",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Ondersteuning",
|
"support": "Ondersteuning",
|
||||||
"transactionHistory": "Transactiegeschiedenis"
|
"transactionHistory": "Transactiegeschiedenis"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Contact opnemen",
|
||||||
|
"text": "Als u vragen heeft over het spel, neem dan contact op met ons ondersteuningsteam.",
|
||||||
|
"title": "Ondersteuning"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"operationType": "Bewerkingstype",
|
"operationType": "Bewerkingstype",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "wył",
|
"off": "wył",
|
||||||
"on": "wł"
|
"on": "wł"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Odpowiedź",
|
||||||
|
"1q": "Pytanie",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Pasieka",
|
"apiary": "Pasieka",
|
||||||
"cashdesk": "Kasa",
|
"cashdesk": "Kasa",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "z"
|
"of": "z"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Skontaktuj się z pomocą",
|
|
||||||
"text": "Jeśli masz pytania dotyczące gry, skontaktuj się z naszym zespołem wsparcia."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Informacje o koncie",
|
"accountInfo": "Informacje o koncie",
|
||||||
"back": "Wstecz",
|
"back": "Wstecz",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Wsparcie",
|
"support": "Wsparcie",
|
||||||
"transactionHistory": "Historia transakcji"
|
"transactionHistory": "Historia transakcji"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Skontaktuj się z pomocą",
|
||||||
|
"text": "Jeśli masz pytania dotyczące gry, skontaktuj się z naszym zespołem wsparcia.",
|
||||||
|
"title": "Wsparcie"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Data",
|
"date": "Data",
|
||||||
"operationType": "Typ operacji",
|
"operationType": "Typ operacji",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "não",
|
"off": "não",
|
||||||
"on": "sim"
|
"on": "sim"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Resposta",
|
||||||
|
"1q": "Pergunta",
|
||||||
|
"title": "FAQ"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Apiário",
|
"apiary": "Apiário",
|
||||||
"cashdesk": "Caixa",
|
"cashdesk": "Caixa",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "de"
|
"of": "de"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Contactar suporte",
|
|
||||||
"text": "Se tiver dúvidas relacionadas ao jogo, entre em contato com nossa equipe de suporte."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Informações da conta",
|
"accountInfo": "Informações da conta",
|
||||||
"back": "Voltar",
|
"back": "Voltar",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Suporte",
|
"support": "Suporte",
|
||||||
"transactionHistory": "Histórico de transações"
|
"transactionHistory": "Histórico de transações"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Contactar suporte",
|
||||||
|
"text": "Se tiver dúvidas relacionadas ao jogo, entre em contato com nossa equipe de suporte.",
|
||||||
|
"title": "Suporte"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Data",
|
"date": "Data",
|
||||||
"operationType": "Tipo de operação",
|
"operationType": "Tipo de operação",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "выкл",
|
"off": "выкл",
|
||||||
"on": "вкл"
|
"on": "вкл"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Ответ",
|
||||||
|
"1q": "Вопрос",
|
||||||
|
"title": "ЧаВо"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Пасека",
|
"apiary": "Пасека",
|
||||||
"cashdesk": "Касса",
|
"cashdesk": "Касса",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "из"
|
"of": "из"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Связаться с поддержкой",
|
|
||||||
"text": "Если у вас возникли вопросы, связанные с игрой — обратитесь в нашу службу поддержки."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Информация об аккаунте",
|
"accountInfo": "Информация об аккаунте",
|
||||||
"back": "Назад",
|
"back": "Назад",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Поддержка",
|
"support": "Поддержка",
|
||||||
"transactionHistory": "История транзакций"
|
"transactionHistory": "История транзакций"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Связаться с поддержкой",
|
||||||
|
"text": "Если у вас возникли вопросы, связанные с игрой — обратитесь в нашу службу поддержки.",
|
||||||
|
"title": "Поддержка"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Дата",
|
"date": "Дата",
|
||||||
"operationType": "Тип операции",
|
"operationType": "Тип операции",
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
"off": "kapalı",
|
"off": "kapalı",
|
||||||
"on": "açık"
|
"on": "açık"
|
||||||
},
|
},
|
||||||
|
"faq": {
|
||||||
|
"1a": "Cevap",
|
||||||
|
"1q": "Soru",
|
||||||
|
"title": "SSS"
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"apiary": "Arılık",
|
"apiary": "Arılık",
|
||||||
"cashdesk": "Kasa",
|
"cashdesk": "Kasa",
|
||||||
@@ -31,10 +36,6 @@
|
|||||||
"pagination": {
|
"pagination": {
|
||||||
"of": "/"
|
"of": "/"
|
||||||
},
|
},
|
||||||
"support": {
|
|
||||||
"action": "Destek ile iletişime geç",
|
|
||||||
"text": "Oyunla ilgili sorularınız varsa lütfen destek ekibimizle iletişime geçin."
|
|
||||||
},
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"accountInfo": "Hesap bilgileri",
|
"accountInfo": "Hesap bilgileri",
|
||||||
"back": "Geri",
|
"back": "Geri",
|
||||||
@@ -44,6 +45,11 @@
|
|||||||
"support": "Destek",
|
"support": "Destek",
|
||||||
"transactionHistory": "İşlem Geçmişi"
|
"transactionHistory": "İşlem Geçmişi"
|
||||||
},
|
},
|
||||||
|
"support": {
|
||||||
|
"action": "Destek ile iletişime geç",
|
||||||
|
"text": "Oyunla ilgili sorularınız varsa lütfen destek ekibimizle iletişime geçin.",
|
||||||
|
"title": "Destek"
|
||||||
|
},
|
||||||
"transactionHistory": {
|
"transactionHistory": {
|
||||||
"date": "Tarih",
|
"date": "Tarih",
|
||||||
"operationType": "İşlem türü",
|
"operationType": "İşlem türü",
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
import { type ReactNode, type RefObject, useMemo } from "react";
|
|
||||||
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
||||||
import tg, { STORAGE_KEYS } from "@/tg";
|
|
||||||
import type { SoundKey } from "./sounds";
|
|
||||||
import { SOUNDS, preloadSounds } from "./sounds";
|
|
||||||
|
|
||||||
type AudioCtxValue = {
|
|
||||||
isEnabled: boolean;
|
|
||||||
setIsEnabled: (enabled: boolean) => void;
|
|
||||||
loopingRef: RefObject<Map<SoundKey, HTMLAudioElement>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AudioCtx = createContext<AudioCtxValue | null>(null);
|
|
||||||
|
|
||||||
export function useAudioSettings(): {
|
|
||||||
isEnabled: boolean;
|
|
||||||
setIsEnabled: (enabled: boolean) => void;
|
|
||||||
} {
|
|
||||||
const ctx = useContext(AudioCtx);
|
|
||||||
if (!ctx) throw new Error("useAudioSettings must be used within AudioProvider");
|
|
||||||
return { isEnabled: ctx.isEnabled, setIsEnabled: ctx.setIsEnabled };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePlaySound(): (
|
|
||||||
key: SoundKey,
|
|
||||||
{ mode, force }?: { mode?: "once" | "loop"; force: boolean },
|
|
||||||
) => void {
|
|
||||||
const ctx = useContext(AudioCtx);
|
|
||||||
if (!ctx) throw new Error("usePlaySound must be used within AudioProvider");
|
|
||||||
|
|
||||||
const { isEnabled, loopingRef } = ctx;
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
(key: SoundKey, { mode, force }: { mode?: "once" | "loop"; force?: boolean } = {}) => {
|
|
||||||
if (!isEnabled && !force) return;
|
|
||||||
|
|
||||||
if (mode === "loop" && loopingRef.current.has(key)) return;
|
|
||||||
|
|
||||||
const audio = new Audio(SOUNDS[key]);
|
|
||||||
|
|
||||||
if (mode === "loop") {
|
|
||||||
audio.loop = true;
|
|
||||||
loopingRef.current.set(key, audio);
|
|
||||||
} else {
|
|
||||||
audio.addEventListener(
|
|
||||||
"ended",
|
|
||||||
() => {
|
|
||||||
audio.src = "";
|
|
||||||
},
|
|
||||||
{ once: true },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
audio.play().catch(() => {
|
|
||||||
if (mode === "loop") loopingRef.current.delete(key);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[isEnabled, loopingRef],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AudioProvider({ children }: { children: ReactNode }) {
|
|
||||||
const [isEnabled, setIsEnabled] = useState(true);
|
|
||||||
const hydratedRef = useRef(false);
|
|
||||||
const loopingRef = useRef<Map<SoundKey, HTMLAudioElement>>(null);
|
|
||||||
if (!loopingRef.current) {
|
|
||||||
loopingRef.current = new Map();
|
|
||||||
preloadSounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
tg.storage.getItem(STORAGE_KEYS.soundEnabled).then((value) => {
|
|
||||||
if (value !== "") {
|
|
||||||
setIsEnabled(value !== "false");
|
|
||||||
}
|
|
||||||
hydratedRef.current = true;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hydratedRef.current) return;
|
|
||||||
tg.storage.setItem(STORAGE_KEYS.soundEnabled, String(isEnabled));
|
|
||||||
}, [isEnabled]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isEnabled) {
|
|
||||||
for (const audio of loopingRef.current!.values()) {
|
|
||||||
audio.pause();
|
|
||||||
audio.src = "";
|
|
||||||
}
|
|
||||||
loopingRef.current!.clear();
|
|
||||||
}
|
|
||||||
}, [isEnabled]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AudioCtx
|
|
||||||
value={useMemo(
|
|
||||||
() =>
|
|
||||||
({
|
|
||||||
isEnabled,
|
|
||||||
setIsEnabled,
|
|
||||||
loopingRef,
|
|
||||||
}) as AudioCtxValue,
|
|
||||||
[isEnabled],
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</AudioCtx>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
export { AudioProvider, useAudioSettings, usePlaySound } from "./AudioContext";
|
export { useAudioStore } from "./store";
|
||||||
export { SOUNDS } from "./sounds";
|
export { SOUNDS } from "./sounds";
|
||||||
export type { SoundKey } from "./sounds";
|
export type { SoundKey } from "./sounds";
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import { prefetch } from "@/helpers/dom";
|
import { Howl } from "howler";
|
||||||
|
|
||||||
import click from "./assets/click.mp3";
|
import click from "./assets/click.mp3";
|
||||||
|
|
||||||
export const SOUNDS = {
|
const SOUND_SOURCES = {
|
||||||
click,
|
click,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type SoundKey = keyof typeof SOUNDS;
|
export type SoundKey = keyof typeof SOUND_SOURCES;
|
||||||
|
|
||||||
|
export const SOUNDS: Record<SoundKey, Howl> = {} as Record<SoundKey, Howl>;
|
||||||
|
|
||||||
export function preloadSounds() {
|
export function preloadSounds() {
|
||||||
for (const src of Object.values(SOUNDS)) {
|
for (const [key, src] of Object.entries(SOUND_SOURCES)) {
|
||||||
prefetch(src);
|
SOUNDS[key as SoundKey] = new Howl({ src: [src], preload: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/audio/store.ts
Normal file
76
src/audio/store.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import tg, { STORAGE_KEYS } from "@/tg";
|
||||||
|
import type { SoundKey } from "./sounds";
|
||||||
|
import { SOUNDS, preloadSounds } from "./sounds";
|
||||||
|
|
||||||
|
type AudioState = {
|
||||||
|
isEnabled: boolean;
|
||||||
|
_hydrated: boolean;
|
||||||
|
setIsEnabled: (enabled: boolean) => void;
|
||||||
|
hydrate: () => Promise<void>;
|
||||||
|
play: (key: SoundKey, opts?: { mode?: "once" | "loop"; force?: boolean }) => void;
|
||||||
|
stopLoop: (key: SoundKey) => void;
|
||||||
|
stopAllLoops: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const looping = new Map<SoundKey, number>();
|
||||||
|
|
||||||
|
export const useAudioStore = create<AudioState>((set, get) => {
|
||||||
|
preloadSounds();
|
||||||
|
|
||||||
|
return {
|
||||||
|
isEnabled: true,
|
||||||
|
_hydrated: false,
|
||||||
|
|
||||||
|
setIsEnabled(enabled: boolean) {
|
||||||
|
set({ isEnabled: enabled });
|
||||||
|
if (get()._hydrated) {
|
||||||
|
tg.storage.setItem(STORAGE_KEYS.soundEnabled, String(enabled));
|
||||||
|
}
|
||||||
|
if (!enabled) {
|
||||||
|
get().stopAllLoops();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async hydrate() {
|
||||||
|
const value = await tg.storage.getItem(STORAGE_KEYS.soundEnabled);
|
||||||
|
if (value !== "") {
|
||||||
|
set({ isEnabled: value !== "false" });
|
||||||
|
}
|
||||||
|
set({ _hydrated: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
play(key: SoundKey, opts?: { mode?: "once" | "loop"; force?: boolean }) {
|
||||||
|
const { isEnabled } = get();
|
||||||
|
if (!isEnabled && !opts?.force) return;
|
||||||
|
|
||||||
|
const howl = SOUNDS[key];
|
||||||
|
if (!howl) return;
|
||||||
|
|
||||||
|
if (opts?.mode === "loop") {
|
||||||
|
if (looping.has(key)) return;
|
||||||
|
howl.loop(true);
|
||||||
|
const id = howl.play();
|
||||||
|
looping.set(key, id);
|
||||||
|
} else {
|
||||||
|
howl.loop(false);
|
||||||
|
howl.play();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stopLoop(key: SoundKey) {
|
||||||
|
const id = looping.get(key);
|
||||||
|
if (id != null) {
|
||||||
|
SOUNDS[key]?.stop(id);
|
||||||
|
looping.delete(key);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stopAllLoops() {
|
||||||
|
for (const [key, id] of looping) {
|
||||||
|
SOUNDS[key]?.stop(id);
|
||||||
|
}
|
||||||
|
looping.clear();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { motion, type HTMLMotionProps } from "motion/react";
|
import { motion, type HTMLMotionProps } from "motion/react";
|
||||||
import clsx, { type ClassValue } from "clsx";
|
import clsx, { type ClassValue } from "clsx";
|
||||||
|
|
||||||
import { usePlaySound } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
import tg from "@/tg";
|
import tg from "@/tg";
|
||||||
|
|
||||||
import classes from "./Button.module.css";
|
import classes from "./Button.module.css";
|
||||||
@@ -19,7 +19,7 @@ const VARIANTS_MAP = {
|
|||||||
} satisfies Record<Exclude<Props["variant"], undefined>, string>;
|
} satisfies Record<Exclude<Props["variant"], undefined>, string>;
|
||||||
|
|
||||||
export default function Button({ className, variant = "blue", onClick, ...props }: Props) {
|
export default function Button({ className, variant = "blue", onClick, ...props }: Props) {
|
||||||
const play = usePlaySound();
|
const play = useAudioStore((s) => s.play);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.button
|
<motion.button
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import LightSurface from "@components/surface/LightSurface";
|
|||||||
import BackIcon from "./icons/BackIcon";
|
import BackIcon from "./icons/BackIcon";
|
||||||
import StartIcon from "./icons/StartIcon";
|
import StartIcon from "./icons/StartIcon";
|
||||||
import classes from "./Pagination.module.css";
|
import classes from "./Pagination.module.css";
|
||||||
import { usePlaySound } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: number;
|
value: number;
|
||||||
@@ -21,7 +21,7 @@ type Props = {
|
|||||||
|
|
||||||
export default function Pagination({ value, total, onChange, variant = "default" }: Props) {
|
export default function Pagination({ value, total, onChange, variant = "default" }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const play = usePlaySound();
|
const play = useAudioStore((s) => s.play);
|
||||||
|
|
||||||
const isAtStart = value <= 1 || total <= 1;
|
const isAtStart = value <= 1 || total <= 1;
|
||||||
const isAtEnd = value >= total || total <= 1;
|
const isAtEnd = value >= total || total <= 1;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import DarkSurface from "@components/surface/DarkSurface";
|
|||||||
import { motion, type HTMLMotionProps } from "motion/react";
|
import { motion, type HTMLMotionProps } from "motion/react";
|
||||||
|
|
||||||
import classes from "./TabSelector.module.css";
|
import classes from "./TabSelector.module.css";
|
||||||
import { usePlaySound } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
import tg from "@/tg";
|
import tg from "@/tg";
|
||||||
|
|
||||||
type Tab = {
|
type Tab = {
|
||||||
@@ -20,7 +20,7 @@ type Props = Omit<HTMLMotionProps<"div">, "className" | "onChange"> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function TabSelector({ tabs, value, onChange, className, ...props }: Props) {
|
export default function TabSelector({ tabs, value, onChange, className, ...props }: Props) {
|
||||||
const play = usePlaySound();
|
const play = useAudioStore((s) => s.play);
|
||||||
|
|
||||||
const selectedIndex = value != null ? tabs.findIndex((tab) => tab.key === value) : -1;
|
const selectedIndex = value != null ? tabs.findIndex((tab) => tab.key === value) : -1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { motion, type HTMLMotionProps } from "motion/react";
|
import { motion, type HTMLMotionProps } from "motion/react";
|
||||||
import clsx, { type ClassValue } from "clsx";
|
import clsx, { type ClassValue } from "clsx";
|
||||||
import { type ReactNode, useRef, useState, type ChangeEvent, useId } from "react";
|
import { type ReactNode, useRef, useState, type ChangeEvent, type FocusEvent, useId } from "react";
|
||||||
import KeyboardIcon from "@components/icons/KeyboardIcon";
|
import KeyboardIcon from "@components/icons/KeyboardIcon";
|
||||||
|
|
||||||
import classes from "./NumberInput.module.css";
|
import classes from "./NumberInput.module.css";
|
||||||
import { usePlaySound } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
import tg from "@/tg";
|
import tg from "@/tg";
|
||||||
|
|
||||||
type Props = Omit<HTMLMotionProps<"input">, "className" | "type" | "onChange"> & {
|
type Props = Omit<HTMLMotionProps<"input">, "className" | "type" | "onChange"> & {
|
||||||
@@ -29,9 +29,10 @@ export default function NumberInput({
|
|||||||
prefix,
|
prefix,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
onFocus,
|
||||||
...props
|
...props
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const play = usePlaySound();
|
const play = useAudioStore((s) => s.play);
|
||||||
|
|
||||||
const stableId = useId();
|
const stableId = useId();
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -44,6 +45,16 @@ export default function NumberInput({
|
|||||||
setStrValue(value?.toString() ?? "");
|
setStrValue(value?.toString() ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
|
||||||
|
onFocus?.(e);
|
||||||
|
const target = e.target;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (target.isConnected) {
|
||||||
|
target.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const normalized = filterNumericInput(e.target.value);
|
const normalized = filterNumericInput(e.target.value);
|
||||||
setStrValue(normalized);
|
setStrValue(normalized);
|
||||||
@@ -65,6 +76,7 @@ export default function NumberInput({
|
|||||||
className={classes.input}
|
className={classes.input}
|
||||||
value={strValue}
|
value={strValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
onFocus={handleFocus}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<motion.button
|
<motion.button
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 50%;
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { motion, type HTMLMotionProps } from "motion/react";
|
|||||||
|
|
||||||
import classes from "./SwitchInput.module.css";
|
import classes from "./SwitchInput.module.css";
|
||||||
import tg from "@/tg";
|
import tg from "@/tg";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
type Props = Omit<HTMLMotionProps<"div">, "className" | "onChange"> & {
|
type Props = Omit<HTMLMotionProps<"div">, "className" | "onChange"> & {
|
||||||
value?: boolean | null;
|
value?: boolean | null;
|
||||||
@@ -15,7 +16,8 @@ type Props = Omit<HTMLMotionProps<"div">, "className" | "onChange"> & {
|
|||||||
|
|
||||||
export default function SwitchInput({ value, onChange, className, ...props }: Props) {
|
export default function SwitchInput({ value, onChange, className, ...props }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const selectedIndex = value != null ? (value ? 0 : 1) : -1;
|
const selectedIndex = value != null ? (value ? 0 : 1) : 0;
|
||||||
|
const selectedIndexRef = useRef(selectedIndex);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentSurface
|
<ContentSurface
|
||||||
@@ -45,12 +47,8 @@ export default function SwitchInput({ value, onChange, className, ...props }: Pr
|
|||||||
{selectedIndex >= 0 && (
|
{selectedIndex >= 0 && (
|
||||||
<LightSurface
|
<LightSurface
|
||||||
className={classes.thumb}
|
className={classes.thumb}
|
||||||
initial={{ scale: 0.5 }}
|
initial={{ left: `${selectedIndexRef.current * 50}%` }}
|
||||||
animate={{
|
animate={{ left: `${selectedIndex * 50}%` }}
|
||||||
left: `${selectedIndex * 50}%`,
|
|
||||||
width: "50%",
|
|
||||||
scale: 1,
|
|
||||||
}}
|
|
||||||
transition={{ type: "spring", stiffness: 500, damping: 35 }}
|
transition={{ type: "spring", stiffness: 500, damping: 35 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { motion, type HTMLMotionProps } from "motion/react";
|
import { motion, type HTMLMotionProps } from "motion/react";
|
||||||
import clsx, { type ClassValue } from "clsx";
|
import clsx, { type ClassValue } from "clsx";
|
||||||
|
import type { FocusEvent } from "react";
|
||||||
|
|
||||||
import classes from "./TextAreaInput.module.css";
|
import classes from "./TextAreaInput.module.css";
|
||||||
|
|
||||||
@@ -8,10 +9,21 @@ type Props = Omit<HTMLMotionProps<"textarea">, "className"> & {
|
|||||||
error?: boolean;
|
error?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TextAreaInput({ className, error, ...props }: Props) {
|
export default function TextAreaInput({ className, error, onFocus, ...props }: Props) {
|
||||||
|
const handleFocus = (e: FocusEvent<HTMLTextAreaElement>) => {
|
||||||
|
onFocus?.(e);
|
||||||
|
const target = e.target;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (target.isConnected) {
|
||||||
|
target.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.textarea
|
<motion.textarea
|
||||||
{...props}
|
{...props}
|
||||||
|
onFocus={handleFocus}
|
||||||
className={clsx(classes.input, error && classes.error, className)}
|
className={clsx(classes.input, error && classes.error, className)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { motion, type HTMLMotionProps } from "motion/react";
|
import { motion, type HTMLMotionProps } from "motion/react";
|
||||||
import clsx, { type ClassValue } from "clsx";
|
import clsx, { type ClassValue } from "clsx";
|
||||||
|
import type { FocusEvent } from "react";
|
||||||
|
|
||||||
import classes from "./TextInput.module.css";
|
import classes from "./TextInput.module.css";
|
||||||
|
|
||||||
@@ -8,11 +9,22 @@ type Props = Omit<HTMLMotionProps<"input">, "className"> & {
|
|||||||
error?: boolean;
|
error?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TextInput({ className, error, ...props }: Props) {
|
export default function TextInput({ className, error, onFocus, ...props }: Props) {
|
||||||
|
const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
|
||||||
|
onFocus?.(e);
|
||||||
|
const target = e.target;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (target.isConnected) {
|
||||||
|
target.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.input
|
<motion.input
|
||||||
{...props}
|
{...props}
|
||||||
type="text"
|
type="text"
|
||||||
|
onFocus={handleFocus}
|
||||||
className={clsx(classes.input, error && classes.error, className)}
|
className={clsx(classes.input, error && classes.error, className)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -324,8 +324,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="127.651"
|
y2="127.651"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#FFD844" />
|
<stop stopColor="#FFD844" />
|
||||||
<stop offset="1" stop-color="#FDB922" />
|
<stop offset="1" stopColor="#FDB922" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint1_linear_227_12316"
|
id="paint1_linear_227_12316"
|
||||||
@@ -335,8 +335,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="110.77"
|
y2="110.77"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#FFD844" />
|
<stop stopColor="#FFD844" />
|
||||||
<stop offset="1" stop-color="#FDB922" />
|
<stop offset="1" stopColor="#FDB922" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint2_linear_227_12316"
|
id="paint2_linear_227_12316"
|
||||||
@@ -346,8 +346,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="64.1507"
|
y2="64.1507"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#FFD844" />
|
<stop stopColor="#FFD844" />
|
||||||
<stop offset="1" stop-color="#FDB922" />
|
<stop offset="1" stopColor="#FDB922" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint3_linear_227_12316"
|
id="paint3_linear_227_12316"
|
||||||
@@ -357,8 +357,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="154.721"
|
y2="154.721"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#CB5402" />
|
<stop stopColor="#CB5402" />
|
||||||
<stop offset="1" stop-color="#F07D02" />
|
<stop offset="1" stopColor="#F07D02" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint4_linear_227_12316"
|
id="paint4_linear_227_12316"
|
||||||
@@ -368,8 +368,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="100.601"
|
y2="100.601"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#CB5402" />
|
<stop stopColor="#CB5402" />
|
||||||
<stop offset="1" stop-color="#F07D02" />
|
<stop offset="1" stopColor="#F07D02" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint5_linear_227_12316"
|
id="paint5_linear_227_12316"
|
||||||
@@ -379,8 +379,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="63.6805"
|
y2="63.6805"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#CB5402" />
|
<stop stopColor="#CB5402" />
|
||||||
<stop offset="1" stop-color="#F07D02" />
|
<stop offset="1" stopColor="#F07D02" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<radialGradient
|
<radialGradient
|
||||||
id="paint6_radial_227_12316"
|
id="paint6_radial_227_12316"
|
||||||
@@ -390,8 +390,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
gradientTransform="translate(167.7 150.321) scale(59.48)"
|
gradientTransform="translate(167.7 150.321) scale(59.48)"
|
||||||
>
|
>
|
||||||
<stop stop-color="#F37900" />
|
<stop stopColor="#F37900" />
|
||||||
<stop offset="1" stop-color="#FFCA13" />
|
<stop offset="1" stopColor="#FFCA13" />
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
<radialGradient
|
<radialGradient
|
||||||
id="paint7_radial_227_12316"
|
id="paint7_radial_227_12316"
|
||||||
@@ -401,8 +401,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
gradientTransform="translate(89.3202 89.8705) scale(69.51 69.51)"
|
gradientTransform="translate(89.3202 89.8705) scale(69.51 69.51)"
|
||||||
>
|
>
|
||||||
<stop stop-color="#F37900" />
|
<stop stopColor="#F37900" />
|
||||||
<stop offset="1" stop-color="#FFCA13" />
|
<stop offset="1" stopColor="#FFCA13" />
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
<radialGradient
|
<radialGradient
|
||||||
id="paint8_radial_227_12316"
|
id="paint8_radial_227_12316"
|
||||||
@@ -412,8 +412,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
gradientTransform="translate(207.66 68.3504) scale(75.09 75.0899)"
|
gradientTransform="translate(207.66 68.3504) scale(75.09 75.0899)"
|
||||||
>
|
>
|
||||||
<stop stop-color="#F37900" />
|
<stop stopColor="#F37900" />
|
||||||
<stop offset="1" stop-color="#FFCA13" />
|
<stop offset="1" stopColor="#FFCA13" />
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint9_linear_227_12316"
|
id="paint9_linear_227_12316"
|
||||||
@@ -423,8 +423,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="114.091"
|
y2="114.091"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#F5AD11" />
|
<stop stopColor="#F5AD11" />
|
||||||
<stop offset="1" stop-color="#CD7015" />
|
<stop offset="1" stopColor="#CD7015" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint10_linear_227_12316"
|
id="paint10_linear_227_12316"
|
||||||
@@ -434,8 +434,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="165.14"
|
y2="165.14"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#F5AD11" />
|
<stop stopColor="#F5AD11" />
|
||||||
<stop offset="1" stop-color="#CD7015" />
|
<stop offset="1" stopColor="#CD7015" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint11_linear_227_12316"
|
id="paint11_linear_227_12316"
|
||||||
@@ -445,8 +445,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="77.0305"
|
y2="77.0305"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#F5AD11" />
|
<stop stopColor="#F5AD11" />
|
||||||
<stop offset="1" stop-color="#CD7015" />
|
<stop offset="1" stopColor="#CD7015" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint12_linear_227_12316"
|
id="paint12_linear_227_12316"
|
||||||
@@ -456,8 +456,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="153.36"
|
y2="153.36"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#CA6617" stop-opacity="0.4" />
|
<stop stopColor="#CA6617" stopOpacity="0.4" />
|
||||||
<stop offset="1" stop-color="#C36216" stop-opacity="0.6" />
|
<stop offset="1" stopColor="#C36216" stopOpacity="0.6" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint13_linear_227_12316"
|
id="paint13_linear_227_12316"
|
||||||
@@ -467,8 +467,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="116.26"
|
y2="116.26"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop stop-color="#CA6617" stop-opacity="0.4" />
|
<stop stopColor="#CA6617" stopOpacity="0.4" />
|
||||||
<stop offset="1" stop-color="#C36216" stop-opacity="0.6" />
|
<stop offset="1" stopColor="#C36216" stopOpacity="0.6" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint14_linear_227_12316"
|
id="paint14_linear_227_12316"
|
||||||
@@ -478,8 +478,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="79.4803"
|
y2="79.4803"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop offset="0.08" stop-color="#FFFBF3" />
|
<stop offset="0.08" stopColor="#FFFBF3" />
|
||||||
<stop offset="1" stop-color="#FFFBF3" stop-opacity="0.4" />
|
<stop offset="1" stopColor="#FFFBF3" stopOpacity="0.4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint15_linear_227_12316"
|
id="paint15_linear_227_12316"
|
||||||
@@ -489,8 +489,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="110.991"
|
y2="110.991"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop offset="0.08" stop-color="#FFFBF3" />
|
<stop offset="0.08" stopColor="#FFFBF3" />
|
||||||
<stop offset="1" stop-color="#FFFBF3" stop-opacity="0.4" />
|
<stop offset="1" stopColor="#FFFBF3" stopOpacity="0.4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint16_linear_227_12316"
|
id="paint16_linear_227_12316"
|
||||||
@@ -500,8 +500,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="52.5508"
|
y2="52.5508"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop offset="0.08" stop-color="#FFFBF3" />
|
<stop offset="0.08" stopColor="#FFFBF3" />
|
||||||
<stop offset="1" stop-color="#FFFBF3" stop-opacity="0.4" />
|
<stop offset="1" stopColor="#FFFBF3" stopOpacity="0.4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint17_linear_227_12316"
|
id="paint17_linear_227_12316"
|
||||||
@@ -511,8 +511,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="23.7907"
|
y2="23.7907"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop offset="0.08" stop-color="#FFFBF3" />
|
<stop offset="0.08" stopColor="#FFFBF3" />
|
||||||
<stop offset="1" stop-color="#FFFBF3" stop-opacity="0.4" />
|
<stop offset="1" stopColor="#FFFBF3" stopOpacity="0.4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint18_linear_227_12316"
|
id="paint18_linear_227_12316"
|
||||||
@@ -522,8 +522,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="33.1406"
|
y2="33.1406"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop offset="0.08" stop-color="#FFFBF3" />
|
<stop offset="0.08" stopColor="#FFFBF3" />
|
||||||
<stop offset="1" stop-color="#FFFBF3" stop-opacity="0.4" />
|
<stop offset="1" stopColor="#FFFBF3" stopOpacity="0.4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint19_linear_227_12316"
|
id="paint19_linear_227_12316"
|
||||||
@@ -533,8 +533,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="70.1908"
|
y2="70.1908"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop offset="0.08" stop-color="#FFFBF3" />
|
<stop offset="0.08" stopColor="#FFFBF3" />
|
||||||
<stop offset="1" stop-color="#FFFBF3" stop-opacity="0.4" />
|
<stop offset="1" stopColor="#FFFBF3" stopOpacity="0.4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint20_linear_227_12316"
|
id="paint20_linear_227_12316"
|
||||||
@@ -544,8 +544,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="75.8007"
|
y2="75.8007"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop offset="0.08" stop-color="#FFFBF3" />
|
<stop offset="0.08" stopColor="#FFFBF3" />
|
||||||
<stop offset="1" stop-color="#FFFBF3" stop-opacity="0.4" />
|
<stop offset="1" stopColor="#FFFBF3" stopOpacity="0.4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
id="paint21_linear_227_12316"
|
id="paint21_linear_227_12316"
|
||||||
@@ -555,8 +555,8 @@ export default function HoneyIcon(props: Props) {
|
|||||||
y2="70.1004"
|
y2="70.1004"
|
||||||
gradientUnits="userSpaceOnUse"
|
gradientUnits="userSpaceOnUse"
|
||||||
>
|
>
|
||||||
<stop offset="0.08" stop-color="#FFFBF3" />
|
<stop offset="0.08" stopColor="#FFFBF3" />
|
||||||
<stop offset="1" stop-color="#FFFBF3" stop-opacity="0.4" />
|
<stop offset="1" stopColor="#FFFBF3" stopOpacity="0.4" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</motion.svg>
|
</motion.svg>
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { createContext, useCallback, useContext, useRef, useState } from "react";
|
import { createContext, useCallback, useContext, useRef, useState } from "react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { cloneWithoutAnimations } from "./Liftable";
|
||||||
|
|
||||||
type LiftContextValue = {
|
type LiftContextValue = {
|
||||||
liftedIds: Set<string>;
|
liftedIds: Set<string>;
|
||||||
alwaysLiftedIds: Set<string>;
|
alwaysLiftedIds: Set<string>;
|
||||||
|
modalOpen: boolean;
|
||||||
setLiftedIds: (ids: string[]) => void;
|
setLiftedIds: (ids: string[]) => void;
|
||||||
registerAlways: (id: string) => void;
|
registerAlways: (id: string) => void;
|
||||||
unregisterAlways: (id: string) => void;
|
unregisterAlways: (id: string) => void;
|
||||||
|
registerModal: () => void;
|
||||||
|
beginModalClose: () => void;
|
||||||
|
endModalClose: () => void;
|
||||||
portalContainer: HTMLElement | null;
|
portalContainer: HTMLElement | null;
|
||||||
setPortalContainer: (el: HTMLElement | null) => void;
|
setPortalContainer: (el: HTMLElement | null) => void;
|
||||||
|
setCloneContainer: (el: HTMLElement | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LiftContext = createContext<LiftContextValue | null>(null);
|
const LiftContext = createContext<LiftContextValue | null>(null);
|
||||||
@@ -22,13 +28,41 @@ export function useLift(): LiftContextValue {
|
|||||||
export function LiftProvider({ children }: { children: ReactNode }) {
|
export function LiftProvider({ children }: { children: ReactNode }) {
|
||||||
const [liftedIds, setLiftedIdsRaw] = useState<Set<string>>(new Set());
|
const [liftedIds, setLiftedIdsRaw] = useState<Set<string>>(new Set());
|
||||||
const [alwaysLiftedIds, setAlwaysLiftedIds] = useState<Set<string>>(new Set());
|
const [alwaysLiftedIds, setAlwaysLiftedIds] = useState<Set<string>>(new Set());
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
||||||
const alwaysRef = useRef<Set<string>>(new Set());
|
const alwaysRef = useRef<Set<string>>(new Set());
|
||||||
|
const modalCountRef = useRef(0);
|
||||||
|
const cloneContainerRef = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
const setLiftedIds = useCallback((ids: string[]) => {
|
const setLiftedIds = useCallback((ids: string[]) => {
|
||||||
setLiftedIdsRaw(new Set(ids));
|
setLiftedIdsRaw(new Set(ids));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const registerModal = useCallback(() => {
|
||||||
|
modalCountRef.current++;
|
||||||
|
setModalOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const beginModalClose = useCallback(() => {
|
||||||
|
if (portalContainer && cloneContainerRef.current) {
|
||||||
|
cloneWithoutAnimations(portalContainer, cloneContainerRef.current);
|
||||||
|
}
|
||||||
|
modalCountRef.current--;
|
||||||
|
if (modalCountRef.current === 0) {
|
||||||
|
setModalOpen(false);
|
||||||
|
}
|
||||||
|
}, [portalContainer]);
|
||||||
|
|
||||||
|
const endModalClose = useCallback(() => {
|
||||||
|
if (cloneContainerRef.current) {
|
||||||
|
cloneContainerRef.current.innerHTML = "";
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setCloneContainer = useCallback((el: HTMLElement | null) => {
|
||||||
|
cloneContainerRef.current = el;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const registerAlways = useCallback((id: string) => {
|
const registerAlways = useCallback((id: string) => {
|
||||||
alwaysRef.current.add(id);
|
alwaysRef.current.add(id);
|
||||||
setAlwaysLiftedIds(new Set(alwaysRef.current));
|
setAlwaysLiftedIds(new Set(alwaysRef.current));
|
||||||
@@ -44,11 +78,16 @@ export function LiftProvider({ children }: { children: ReactNode }) {
|
|||||||
value={{
|
value={{
|
||||||
liftedIds,
|
liftedIds,
|
||||||
alwaysLiftedIds,
|
alwaysLiftedIds,
|
||||||
|
modalOpen,
|
||||||
setLiftedIds,
|
setLiftedIds,
|
||||||
registerAlways,
|
registerAlways,
|
||||||
unregisterAlways,
|
unregisterAlways,
|
||||||
|
registerModal,
|
||||||
|
beginModalClose,
|
||||||
|
endModalClose,
|
||||||
portalContainer,
|
portalContainer,
|
||||||
setPortalContainer,
|
setPortalContainer,
|
||||||
|
setCloneContainer,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -3,14 +3,26 @@ import { useLift } from "./LiftContext";
|
|||||||
import classes from "./LiftLayer.module.css";
|
import classes from "./LiftLayer.module.css";
|
||||||
|
|
||||||
export function LiftLayer() {
|
export function LiftLayer() {
|
||||||
const { setPortalContainer } = useLift();
|
const { setPortalContainer, setCloneContainer } = useLift();
|
||||||
|
|
||||||
const refCallback = useCallback(
|
const portalRef = useCallback(
|
||||||
(node: HTMLDivElement | null) => {
|
(node: HTMLDivElement | null) => {
|
||||||
setPortalContainer(node);
|
setPortalContainer(node);
|
||||||
},
|
},
|
||||||
[setPortalContainer],
|
[setPortalContainer],
|
||||||
);
|
);
|
||||||
|
|
||||||
return <div ref={refCallback} className={classes.liftLayer} />;
|
const cloneRef = useCallback(
|
||||||
|
(node: HTMLDivElement | null) => {
|
||||||
|
setCloneContainer(node);
|
||||||
|
},
|
||||||
|
[setCloneContainer],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div ref={portalRef} className={classes.liftLayer} />
|
||||||
|
<div ref={cloneRef} className={classes.liftLayer} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,29 @@
|
|||||||
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
import { useEffect, useId, useLayoutEffect, useRef } from "react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { useLift } from "./LiftContext";
|
import { useLift } from "./LiftContext";
|
||||||
|
|
||||||
|
/** Recursively strip CSS animations and transitions from a cloned DOM tree */
|
||||||
|
function stripAnimations(node: Node) {
|
||||||
|
if (node instanceof HTMLElement) {
|
||||||
|
node.style.animation = "none";
|
||||||
|
node.style.transition = "none";
|
||||||
|
node.getAnimations().forEach((a) => a.cancel());
|
||||||
|
}
|
||||||
|
for (const child of Array.from(node.childNodes)) {
|
||||||
|
stripAnimations(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneWithoutAnimations(source: HTMLElement, target: HTMLElement) {
|
||||||
|
target.innerHTML = "";
|
||||||
|
for (const child of Array.from(source.children)) {
|
||||||
|
const clone = child.cloneNode(true) as HTMLElement;
|
||||||
|
stripAnimations(clone);
|
||||||
|
target.appendChild(clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type LiftableProps = {
|
type LiftableProps = {
|
||||||
id?: string;
|
id?: string;
|
||||||
always?: boolean;
|
always?: boolean;
|
||||||
@@ -23,7 +44,7 @@ export function useLiftable<T extends Record<string, unknown> = Record<string, n
|
|||||||
...extraProps
|
...extraProps
|
||||||
} = (options ?? {}) as LiftableProps & Record<string, unknown>;
|
} = (options ?? {}) as LiftableProps & Record<string, unknown>;
|
||||||
const id = idProp ?? autoId;
|
const id = idProp ?? autoId;
|
||||||
const { liftedIds, alwaysLiftedIds, registerAlways, unregisterAlways } = useLift();
|
const { liftedIds, alwaysLiftedIds, modalOpen, registerAlways, unregisterAlways } = useLift();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (always) {
|
if (always) {
|
||||||
@@ -32,7 +53,7 @@ export function useLiftable<T extends Record<string, unknown> = Record<string, n
|
|||||||
}
|
}
|
||||||
}, [always, id, registerAlways, unregisterAlways]);
|
}, [always, id, registerAlways, unregisterAlways]);
|
||||||
|
|
||||||
const isLifted = liftedIds.has(id) || alwaysLiftedIds.has(id);
|
const isLifted = liftedIds.has(id) || (alwaysLiftedIds.has(id) && modalOpen);
|
||||||
|
|
||||||
const element = (
|
const element = (
|
||||||
<Liftable id={id}>{render({ isLifted, ...extraProps } as { isLifted: boolean } & T)}</Liftable>
|
<Liftable id={id}>{render({ isLifted, ...extraProps } as { isLifted: boolean } & T)}</Liftable>
|
||||||
@@ -41,13 +62,24 @@ export function useLiftable<T extends Record<string, unknown> = Record<string, n
|
|||||||
return { id, element };
|
return { id, element };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { cloneWithoutAnimations };
|
||||||
|
|
||||||
export function Liftable({ id: idProp, always, children }: Props) {
|
export function Liftable({ id: idProp, always, children }: Props) {
|
||||||
const autoId = useId();
|
const autoId = useId();
|
||||||
const id = idProp ?? autoId;
|
const id = idProp ?? autoId;
|
||||||
const { liftedIds, alwaysLiftedIds, registerAlways, unregisterAlways, portalContainer } =
|
const {
|
||||||
useLift();
|
liftedIds,
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
alwaysLiftedIds,
|
||||||
const [rect, setRect] = useState<DOMRect | null>(null);
|
modalOpen,
|
||||||
|
registerAlways,
|
||||||
|
unregisterAlways,
|
||||||
|
portalContainer,
|
||||||
|
} = useLift();
|
||||||
|
|
||||||
|
const childrenHostRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inlineSlotRef = useRef<HTMLDivElement>(null);
|
||||||
|
const portalWrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
const wasLiftedRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (always) {
|
if (always) {
|
||||||
@@ -56,29 +88,43 @@ export function Liftable({ id: idProp, always, children }: Props) {
|
|||||||
}
|
}
|
||||||
}, [always, id, registerAlways, unregisterAlways]);
|
}, [always, id, registerAlways, unregisterAlways]);
|
||||||
|
|
||||||
const isLifted = liftedIds.has(id) || alwaysLiftedIds.has(id);
|
const isLifted = liftedIds.has(id) || (alwaysLiftedIds.has(id) && modalOpen);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (isLifted && wrapperRef.current) {
|
const host = childrenHostRef.current;
|
||||||
setRect(wrapperRef.current.getBoundingClientRect());
|
const inlineSlot = inlineSlotRef.current;
|
||||||
}
|
const portalWrapper = portalWrapperRef.current;
|
||||||
if (!isLifted) {
|
if (!host || !inlineSlot || !portalWrapper) return;
|
||||||
setRect(null);
|
|
||||||
|
if (isLifted && !wasLiftedRef.current) {
|
||||||
|
const rect = inlineSlot.getBoundingClientRect();
|
||||||
|
cloneWithoutAnimations(host, inlineSlot);
|
||||||
|
portalWrapper.appendChild(host);
|
||||||
|
portalWrapper.style.cssText = `position:fixed;top:${rect.top}px;left:${rect.left}px;width:${rect.width}px;height:${rect.height}px;pointer-events:auto`;
|
||||||
|
wasLiftedRef.current = true;
|
||||||
|
} else if (!isLifted && wasLiftedRef.current) {
|
||||||
|
inlineSlot.innerHTML = "";
|
||||||
|
inlineSlot.appendChild(host);
|
||||||
|
portalWrapper.style.cssText = "display:none";
|
||||||
|
wasLiftedRef.current = false;
|
||||||
}
|
}
|
||||||
}, [isLifted]);
|
}, [isLifted]);
|
||||||
|
|
||||||
// Re-measure on resize while lifted
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLifted || !wrapperRef.current) return;
|
if (!isLifted || !inlineSlotRef.current || !portalWrapperRef.current) return;
|
||||||
|
const portalWrapper = portalWrapperRef.current;
|
||||||
|
const inlineSlot = inlineSlotRef.current;
|
||||||
|
|
||||||
const measure = () => {
|
const measure = () => {
|
||||||
if (wrapperRef.current) {
|
const rect = inlineSlot.getBoundingClientRect();
|
||||||
setRect(wrapperRef.current.getBoundingClientRect());
|
portalWrapper.style.top = `${rect.top}px`;
|
||||||
}
|
portalWrapper.style.left = `${rect.left}px`;
|
||||||
|
portalWrapper.style.width = `${rect.width}px`;
|
||||||
|
portalWrapper.style.height = `${rect.height}px`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const observer = new ResizeObserver(measure);
|
const observer = new ResizeObserver(measure);
|
||||||
observer.observe(wrapperRef.current);
|
observer.observe(inlineSlot);
|
||||||
window.addEventListener("resize", measure);
|
window.addEventListener("resize", measure);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -87,35 +133,13 @@ export function Liftable({ id: idProp, always, children }: Props) {
|
|||||||
};
|
};
|
||||||
}, [isLifted]);
|
}, [isLifted]);
|
||||||
|
|
||||||
// When lifted and we have measurements + portal target, render in portal
|
|
||||||
if (isLifted && rect && portalContainer) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Placeholder preserves layout space */}
|
<div ref={inlineSlotRef}>
|
||||||
<div
|
<div ref={childrenHostRef}>{children}</div>
|
||||||
ref={wrapperRef}
|
</div>
|
||||||
style={{ width: rect.width, height: rect.height, visibility: "hidden" }}
|
{portalContainer &&
|
||||||
/>
|
createPortal(<div ref={portalWrapperRef} style={{ display: "none" }} />, portalContainer)}
|
||||||
{/* Portal children above blur */}
|
|
||||||
{createPortal(
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: rect.top,
|
|
||||||
left: rect.left,
|
|
||||||
width: rect.width,
|
|
||||||
height: rect.height,
|
|
||||||
pointerEvents: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>,
|
|
||||||
portalContainer,
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal inline rendering
|
|
||||||
return <div ref={wrapperRef}>{children}</div>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
|
max-height: calc(
|
||||||
|
var(--safe-area-height) - var(--header-total) - var(--navigation-total) - 70px
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
@@ -29,9 +32,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: calc(
|
|
||||||
var(--safe-area-height) - var(--header-total) - var(--navigation-total) - 70px
|
|
||||||
);
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 13px;
|
padding: 13px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,18 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Modal({ open, children, onClose, liftIds, title, className }: Props) {
|
export default function Modal({ open, children, onClose, liftIds, title, className }: Props) {
|
||||||
const { setLiftedIds } = useLift();
|
const { setLiftedIds, registerModal, beginModalClose, endModalClose } = useLift();
|
||||||
const prevLiftIdsRef = useRef<string>("");
|
const prevLiftIdsRef = useRef<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
registerModal();
|
||||||
|
return () => {
|
||||||
|
beginModalClose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [open, registerModal, beginModalClose]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const key = liftIds?.join(",") ?? "";
|
const key = liftIds?.join(",") ?? "";
|
||||||
|
|
||||||
@@ -45,7 +54,7 @@ export default function Modal({ open, children, onClose, liftIds, title, classNa
|
|||||||
}, [setLiftedIds]);
|
}, [setLiftedIds]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence onExitComplete={endModalClose}>
|
||||||
{open && (
|
{open && (
|
||||||
<>
|
<>
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
19
src/i18n/resources.d.ts
vendored
19
src/i18n/resources.d.ts
vendored
@@ -1,8 +1,15 @@
|
|||||||
// Auto-generated by i18nextTypesPlugin — do not edit manually
|
// Auto-generated by i18nextTypesPlugin — do not edit manually
|
||||||
declare const resources: {
|
declare const resources: {
|
||||||
|
"accountInfo.paymentBalance": "Einzahlungsguthaben" | "Payment balance" | "Saldo de pagos" | "Solde des paiements" | "Saldo pembayaran" | "Saldo pagamenti" | "Stortingssaldo" | "Saldo wpłat" | "Saldo de pagamentos" | "Баланс пополнений" | "Ödeme bakiyesi";
|
||||||
|
"accountInfo.registrationDate": "Registrierungsdatum" | "Registration date" | "Fecha de registro" | "Date d'inscription" | "Tanggal pendaftaran" | "Data di registrazione" | "Registratiedatum" | "Data rejestracji" | "Data de registro" | "Дата регистрации" | "Kayıt tarihi";
|
||||||
|
"accountInfo.withdrawalBalance": "Auszahlungsguthaben" | "Withdrawal balance" | "Saldo de retiros" | "Solde des retraits" | "Saldo penarikan" | "Saldo prelievi" | "Opnamesaldo" | "Saldo wypłat" | "Saldo de saques" | "Баланс выводов" | "Çekim bakiyesi";
|
||||||
|
"accountInfo.yourId": "Ihre ID" | "Your ID" | "Tu ID" | "Votre ID" | "ID Anda" | "Il tuo ID" | "Uw ID" | "Twoje ID" | "Seu ID" | "Ваш ID" | "Kimliğiniz";
|
||||||
"actionModal.close": "Schließen" | "Close" | "Cerrar" | "Fermer" | "Tutup" | "Chiudi" | "Sluiten" | "Zamknij" | "Fechar" | "Закрыть" | "Kapat";
|
"actionModal.close": "Schließen" | "Close" | "Cerrar" | "Fermer" | "Tutup" | "Chiudi" | "Sluiten" | "Zamknij" | "Fechar" | "Закрыть" | "Kapat";
|
||||||
"common.off": "aus" | "off" | "no" | "non" | "mati" | "uit" | "wył" | "não" | "выкл" | "kapalı";
|
"common.off": "aus" | "off" | "no" | "non" | "mati" | "uit" | "wył" | "não" | "выкл" | "kapalı";
|
||||||
"common.on": "an" | "on" | "sí" | "oui" | "nyala" | "sì" | "aan" | "wł" | "sim" | "вкл" | "açık";
|
"common.on": "an" | "on" | "sí" | "oui" | "nyala" | "sì" | "aan" | "wł" | "sim" | "вкл" | "açık";
|
||||||
|
"faq.1a": "Antwort" | "Answer" | "Respuesta" | "Réponse" | "Jawaban" | "Risposta" | "Antwoord" | "Odpowiedź" | "Resposta" | "Ответ" | "Cevap";
|
||||||
|
"faq.1q": "Frage" | "Question" | "Pregunta" | "Pertanyaan" | "Domanda" | "Vraag" | "Pytanie" | "Pergunta" | "Вопрос" | "Soru";
|
||||||
|
"faq.title": "FAQ" | "ЧаВо" | "SSS";
|
||||||
"nav.apiary": "Bienenhaus" | "Apiary" | "Apiario" | "Rucher" | "Peternakan Lebah" | "Bijenstal" | "Pasieka" | "Apiário" | "Пасека" | "Arılık";
|
"nav.apiary": "Bienenhaus" | "Apiary" | "Apiario" | "Rucher" | "Peternakan Lebah" | "Bijenstal" | "Pasieka" | "Apiário" | "Пасека" | "Arılık";
|
||||||
"nav.cashdesk": "Kasse" | "Cashdesk" | "Caja" | "Caisse" | "Kasir" | "Cassa" | "Kassa" | "Kasa" | "Caixa" | "Касса";
|
"nav.cashdesk": "Kasse" | "Cashdesk" | "Caja" | "Caisse" | "Kasir" | "Cassa" | "Kassa" | "Kasa" | "Caixa" | "Касса";
|
||||||
"nav.earnings": "Einnahmen" | "Earnings" | "Ganancias" | "Gains" | "Penghasilan" | "Guadagni" | "Inkomsten" | "Zarobki" | "Ganhos" | "Заработок" | "Kazançlar";
|
"nav.earnings": "Einnahmen" | "Earnings" | "Ganancias" | "Gains" | "Penghasilan" | "Guadagni" | "Inkomsten" | "Zarobki" | "Ganhos" | "Заработок" | "Kazançlar";
|
||||||
@@ -11,6 +18,10 @@ declare const resources: {
|
|||||||
"nav.roulette": "Roulette" | "Ruleta" | "Ruletka" | "Roleta" | "Рулетка" | "Rulet";
|
"nav.roulette": "Roulette" | "Ruleta" | "Ruletka" | "Roleta" | "Рулетка" | "Rulet";
|
||||||
"nav.shop": "Shop" | "Tienda" | "Boutique" | "Toko" | "Negozio" | "Winkel" | "Sklep" | "Loja" | "Магазин" | "Mağaza";
|
"nav.shop": "Shop" | "Tienda" | "Boutique" | "Toko" | "Negozio" | "Winkel" | "Sklep" | "Loja" | "Магазин" | "Mağaza";
|
||||||
"nav.tasks": "Aufgaben" | "Tasks" | "Tareas" | "Tâches" | "Tugas" | "Compiti" | "Taken" | "Zadania" | "Tarefas" | "Задания" | "Görevler";
|
"nav.tasks": "Aufgaben" | "Tasks" | "Tareas" | "Tâches" | "Tugas" | "Compiti" | "Taken" | "Zadania" | "Tarefas" | "Задания" | "Görevler";
|
||||||
|
"operationType.deposit": "Einzahlung" | "Deposit" | "Depósito" | "Dépôt" | "Setoran" | "Deposito" | "Storting" | "Wpłata" | "Пополнение" | "Yatırım";
|
||||||
|
"operationType.greeting": "Willkommensbonus" | "Greeting bonus" | "Bono de bienvenida" | "Bonus de bienvenue" | "Bonus sambutan" | "Bonus di benvenuto" | "Welkomstbonus" | "Bonus powitalny" | "Bônus de boas-vindas" | "Приветственный бонус" | "Hoş geldin bonusu";
|
||||||
|
"operationType.referral": "Empfehlungsbonus" | "Referral bonus" | "Bono de referido" | "Bonus de parrainage" | "Bonus referral" | "Verwijzingsbonus" | "Bonus polecający" | "Bônus de indicação" | "Реферальный бонус" | "Referans bonusu";
|
||||||
|
"operationType.withdrawal": "Auszahlung" | "Withdrawal" | "Retiro" | "Retrait" | "Penarikan" | "Prelievo" | "Opname" | "Wypłata" | "Saque" | "Вывод" | "Çekim";
|
||||||
"pagination.of": "von" | "of" | "de" | "sur" | "dari" | "di" | "van" | "z" | "из" | "/";
|
"pagination.of": "von" | "of" | "de" | "sur" | "dari" | "di" | "van" | "z" | "из" | "/";
|
||||||
"settings.accountInfo": "Kontoinformationen" | "Account information" | "Información de la cuenta" | "Informations du compte" | "Informasi akun" | "Informazioni account" | "Accountinformatie" | "Informacje o koncie" | "Informações da conta" | "Информация об аккаунте" | "Hesap bilgileri";
|
"settings.accountInfo": "Kontoinformationen" | "Account information" | "Información de la cuenta" | "Informations du compte" | "Informasi akun" | "Informazioni account" | "Accountinformatie" | "Informacje o koncie" | "Informações da conta" | "Информация об аккаунте" | "Hesap bilgileri";
|
||||||
"settings.back": "Zurück" | "Back" | "Volver" | "Retour" | "Kembali" | "Indietro" | "Terug" | "Wstecz" | "Voltar" | "Назад" | "Geri";
|
"settings.back": "Zurück" | "Back" | "Volver" | "Retour" | "Kembali" | "Indietro" | "Terug" | "Wstecz" | "Voltar" | "Назад" | "Geri";
|
||||||
@@ -19,5 +30,13 @@ declare const resources: {
|
|||||||
"settings.sound": "Ton" | "Sound" | "Sonido" | "Son" | "Suara" | "Suono" | "Geluid" | "Dźwięk" | "Som" | "Звук" | "Ses";
|
"settings.sound": "Ton" | "Sound" | "Sonido" | "Son" | "Suara" | "Suono" | "Geluid" | "Dźwięk" | "Som" | "Звук" | "Ses";
|
||||||
"settings.support": "Support" | "Soporte" | "Dukungan" | "Supporto" | "Ondersteuning" | "Wsparcie" | "Suporte" | "Поддержка" | "Destek";
|
"settings.support": "Support" | "Soporte" | "Dukungan" | "Supporto" | "Ondersteuning" | "Wsparcie" | "Suporte" | "Поддержка" | "Destek";
|
||||||
"settings.transactionHistory": "Transaktionsverlauf" | "Transaction History" | "Historial de transacciones" | "Historique des transactions" | "Riwayat transaksi" | "Cronologia transazioni" | "Transactiegeschiedenis" | "Historia transakcji" | "Histórico de transações" | "История транзакций" | "İşlem Geçmişi";
|
"settings.transactionHistory": "Transaktionsverlauf" | "Transaction History" | "Historial de transacciones" | "Historique des transactions" | "Riwayat transaksi" | "Cronologia transazioni" | "Transactiegeschiedenis" | "Historia transakcji" | "Histórico de transações" | "История транзакций" | "İşlem Geçmişi";
|
||||||
|
"support.action": "Support kontaktieren" | "Contact support" | "Contactar soporte" | "Contacter le support" | "Hubungi dukungan" | "Contatta il supporto" | "Contact opnemen" | "Skontaktuj się z pomocą" | "Contactar suporte" | "Связаться с поддержкой" | "Destek ile iletişime geç";
|
||||||
|
"support.text": "Wenn Sie Fragen zum Spiel haben, wenden Sie sich bitte an unseren Support." | "If you have any questions related to the game, please contact our support team." | "Si tienes alguna pregunta relacionada con el juego, contacta con nuestro equipo de soporte." | "Si vous avez des questions liées au jeu, veuillez contacter notre équipe de support." | "Jika Anda memiliki pertanyaan terkait permainan, silakan hubungi tim dukungan kami." | "Se hai domande relative al gioco, contatta il nostro team di supporto." | "Als u vragen heeft over het spel, neem dan contact op met ons ondersteuningsteam." | "Jeśli masz pytania dotyczące gry, skontaktuj się z naszym zespołem wsparcia." | "Se tiver dúvidas relacionadas ao jogo, entre em contato com nossa equipe de suporte." | "Если у вас возникли вопросы, связанные с игрой — обратитесь в нашу службу поддержки." | "Oyunla ilgili sorularınız varsa lütfen destek ekibimizle iletişime geçin.";
|
||||||
|
"support.title": "Support" | "Soporte" | "Dukungan" | "Supporto" | "Ondersteuning" | "Wsparcie" | "Suporte" | "Поддержка" | "Destek";
|
||||||
|
"transactionHistory.date": "Datum" | "Date" | "Fecha" | "Tanggal" | "Data" | "Дата" | "Tarih";
|
||||||
|
"transactionHistory.operationType": "Vorgangsart" | "Operation type" | "Tipo de operación" | "Type d'opération" | "Jenis operasi" | "Tipo di operazione" | "Bewerkingstype" | "Typ operacji" | "Tipo de operação" | "Тип операции" | "İşlem türü";
|
||||||
|
"transactionHistory.sum": "Summe" | "Sum" | "Suma" | "Somme" | "Jumlah" | "Somma" | "Som" | "Soma" | "Сумма" | "Toplam";
|
||||||
|
"transactionHistory.title": "Transaktionsverlauf" | "Transaction History" | "Historial de transacciones" | "Historique des transactions" | "Riwayat transaksi" | "Cronologia transazioni" | "Transactiegeschiedenis" | "Historia transakcji" | "Histórico de transações" | "История транзакций" | "İşlem Geçmişi";
|
||||||
|
"transactionHistory.yourTransactions": "Ihre Transaktionen" | "Your Transactions" | "Tus transacciones" | "Vos transactions" | "Transaksi Anda" | "Le tue transazioni" | "Uw transacties" | "Twoje transakcje" | "Suas transações" | "Ваши транзакции" | "İşlemleriniz";
|
||||||
};
|
};
|
||||||
export default resources;
|
export default resources;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import tg from "@/tg";
|
import tg from "@/tg";
|
||||||
import { routeTree } from "./routeTree.gen";
|
import { routeTree } from "./routeTree.gen";
|
||||||
import { AudioProvider } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
import { LiftProvider } from "@components/lift";
|
import { LiftProvider } from "@components/lift";
|
||||||
|
|
||||||
import "./styles/index.css";
|
import "./styles/index.css";
|
||||||
@@ -30,15 +30,14 @@ declare module "@tanstack/react-router" {
|
|||||||
|
|
||||||
tg.init();
|
tg.init();
|
||||||
i18n.init();
|
i18n.init();
|
||||||
|
useAudioStore.getState().hydrate();
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryClientProvider client={new QueryClient()}>
|
<QueryClientProvider client={new QueryClient()}>
|
||||||
<AudioProvider>
|
|
||||||
<LiftProvider>
|
<LiftProvider>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</LiftProvider>
|
</LiftProvider>
|
||||||
</AudioProvider>
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
background-image: url("./assets/main-bg.svg");
|
background-image: url("./assets/main-bg.svg");
|
||||||
background-size: auto 101%;
|
background-size: auto 101%;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
BIN
src/routes/-/RootLayout/assets/main-bg.png
Normal file
BIN
src/routes/-/RootLayout/assets/main-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 720 KiB |
@@ -13,8 +13,9 @@
|
|||||||
z-index: 20;
|
z-index: 20;
|
||||||
|
|
||||||
padding-top: var(--header-padding);
|
padding-top: var(--header-padding);
|
||||||
padding-left: var(--safe-left);
|
--padding-x: 10px;
|
||||||
padding-right: var(--safe-right);
|
padding-left: calc(var(--safe-left) + var(--padding-x));
|
||||||
|
padding-right: calc(var(--safe-right) + var(--padding-x));
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|||||||
@@ -12,9 +12,6 @@
|
|||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -53,14 +50,79 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.level {
|
||||||
|
align-self: center;
|
||||||
|
width: 37px;
|
||||||
|
height: 21px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #ac6b33;
|
||||||
|
box-shadow:
|
||||||
|
0px 0.5px 0px 0px #ffefd1 inset,
|
||||||
|
0px -0.5px 0px 0px #0000008c inset;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: -12px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.levelInner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 40px;
|
||||||
|
background: #000000b3;
|
||||||
|
border-top: 1px solid transparent;
|
||||||
|
box-shadow:
|
||||||
|
0px 4px 2px 0px #00000040 inset,
|
||||||
|
0px -1px 0px 0px #fff3b5 inset;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balances {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rightTop {
|
.balanceRow {
|
||||||
|
width: 124px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 17px;
|
||||||
|
background: #00000099;
|
||||||
|
box-shadow:
|
||||||
|
0px -2px 0px 0px #b0703d inset,
|
||||||
|
2px 0px 0px 0px #b0703d inset,
|
||||||
|
-2px 0px 0px 0px #b0703d inset,
|
||||||
|
0px 2px 0px 0px #593313 inset,
|
||||||
|
0px 2px 2px 2px #00000059;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding-inline: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rightBottom {
|
.balanceIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.honeyValue {
|
||||||
|
font-family: "BalsamiqSans", sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #f7d048;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moneyValue {
|
||||||
|
font-family: "BalsamiqSans", sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #6cc872;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ import classes from "./Profile.module.css";
|
|||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import { Liftable } from "@components/lift";
|
import { Liftable } from "@components/lift";
|
||||||
import { useTelegramUser } from "@/tg";
|
import { useTelegramUser } from "@/tg";
|
||||||
|
import HoneyIcon from "@components/icons/HoneyIcon";
|
||||||
|
import MoneyIcon from "@components/icons/MoneyIcon";
|
||||||
|
import Counter from "@components/atoms/Counter";
|
||||||
|
|
||||||
export default function Profile() {
|
export default function Profile() {
|
||||||
const user = useTelegramUser();
|
const user = useTelegramUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Liftable always>
|
<Liftable always>
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -19,10 +23,19 @@ export default function Profile() {
|
|||||||
{user?.photoUrl && <img className={classes.avatarImage} src={user.photoUrl} alt="" />}
|
{user?.photoUrl && <img className={classes.avatarImage} src={user.photoUrl} alt="" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={classes.level}>
|
||||||
|
<div className={classes.levelInner}>1</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={classes.balances}>
|
||||||
|
<div className={classes.balanceRow}>
|
||||||
|
<HoneyIcon className={classes.balanceIcon} />
|
||||||
|
<Counter className={classes.honeyValue} value={129891} />
|
||||||
|
</div>
|
||||||
|
<div className={classes.balanceRow}>
|
||||||
|
<MoneyIcon className={classes.balanceIcon} />
|
||||||
|
<Counter className={classes.moneyValue} value={1781} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.right}>
|
|
||||||
<div className={classes.rightTop} />
|
|
||||||
<div className={classes.rightBottom} />
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Liftable>
|
</Liftable>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
.settings {
|
.settings {
|
||||||
margin-left: auto;
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ const TransactionsHistoryModal = lazy(() => import("./components/TransactionsHis
|
|||||||
const LanguageModal = lazy(() => import("./components/LanguageModal"));
|
const LanguageModal = lazy(() => import("./components/LanguageModal"));
|
||||||
|
|
||||||
import classes from "./Settings.module.css";
|
import classes from "./Settings.module.css";
|
||||||
import { usePlaySound } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const play = usePlaySound();
|
const play = useAudioStore((s) => s.play);
|
||||||
|
|
||||||
const [state, send] = useMachine(settingsMachine);
|
const [state, send] = useMachine(settingsMachine);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Modal from "@components/modals/Modal";
|
|||||||
import ContentSurface from "@components/surface/ContentSurface";
|
import ContentSurface from "@components/surface/ContentSurface";
|
||||||
import DarkSurface from "@components/surface/DarkSurface";
|
import DarkSurface from "@components/surface/DarkSurface";
|
||||||
import { useLanguages } from "@/i18n/useLanguages";
|
import { useLanguages } from "@/i18n/useLanguages";
|
||||||
import { usePlaySound } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
import tg from "@/tg";
|
import tg from "@/tg";
|
||||||
|
|
||||||
import classes from "./LanguageModal.module.css";
|
import classes from "./LanguageModal.module.css";
|
||||||
@@ -21,7 +21,7 @@ type Props = {
|
|||||||
export default function LanguageModal({ open, onClose, onBack }: Props) {
|
export default function LanguageModal({ open, onClose, onBack }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { languages, current, setLanguage } = useLanguages();
|
const { languages, current, setLanguage } = useLanguages();
|
||||||
const play = usePlaySound();
|
const play = useAudioStore((s) => s.play);
|
||||||
|
|
||||||
const handleSelect = (key: string) => {
|
const handleSelect = (key: string) => {
|
||||||
play("click");
|
play("click");
|
||||||
@@ -61,7 +61,11 @@ export default function LanguageModal({ open, onClose, onBack }: Props) {
|
|||||||
|
|
||||||
<ContentSurface className={classes.contentSurface}>
|
<ContentSurface className={classes.contentSurface}>
|
||||||
<DarkSurface className="rounded-full h-full">
|
<DarkSurface className="rounded-full h-full">
|
||||||
<motion.button className={classes.item} onClick={handleBack} whileTap={{ scale: 0.95 }}>
|
<motion.button
|
||||||
|
className={clsx(classes.item, "justify-center text-white text-lg")}
|
||||||
|
onClick={handleBack}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
>
|
||||||
<span>{t("settings.back")}</span>
|
<span>{t("settings.back")}</span>
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</DarkSurface>
|
</DarkSurface>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Modal from "@components/modals/Modal";
|
|||||||
import ContentSurface from "@components/surface/ContentSurface";
|
import ContentSurface from "@components/surface/ContentSurface";
|
||||||
import LightSurface from "@components/surface/LightSurface";
|
import LightSurface from "@components/surface/LightSurface";
|
||||||
import SwitchInput from "@components/form/SwitchInput";
|
import SwitchInput from "@components/form/SwitchInput";
|
||||||
import { useAudioSettings, usePlaySound } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
import tg from "@/tg";
|
import tg from "@/tg";
|
||||||
|
|
||||||
import classes from "./SettingsModal.module.css";
|
import classes from "./SettingsModal.module.css";
|
||||||
@@ -23,8 +23,7 @@ type Props = {
|
|||||||
export default function SettingsModal({ open, onClose, onNavigate, liftIds }: Props) {
|
export default function SettingsModal({ open, onClose, onNavigate, liftIds }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { current } = useLanguages();
|
const { current } = useLanguages();
|
||||||
const { isEnabled, setIsEnabled } = useAudioSettings();
|
const { isEnabled, setIsEnabled, play } = useAudioStore();
|
||||||
const play = usePlaySound();
|
|
||||||
|
|
||||||
const handleNavigate = (modal: SettingsModalId) => {
|
const handleNavigate = (modal: SettingsModalId) => {
|
||||||
play("click");
|
play("click");
|
||||||
|
|||||||
@@ -76,6 +76,10 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.menuOverlay > * {
|
.menuOverlay > * {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { type AnyRoute, useMatchRoute, useNavigate } from "@tanstack/react-router";
|
import { type AnyRoute, useMatchRoute, useNavigate } from "@tanstack/react-router";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AnimatePresence } from "motion/react";
|
import { AnimatePresence } from "motion/react";
|
||||||
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import GlassSurface from "@components/surface/GlassSurface";
|
import GlassSurface from "@components/surface/GlassSurface";
|
||||||
|
|
||||||
@@ -12,7 +13,6 @@ import CashdeskRoute from "@/routes/cashdesk";
|
|||||||
import RouletteRoute from "@/routes/roulette";
|
import RouletteRoute from "@/routes/roulette";
|
||||||
import TasksRoute from "@/routes/tasks";
|
import TasksRoute from "@/routes/tasks";
|
||||||
import EarningsRoute from "@/routes/earnings";
|
import EarningsRoute from "@/routes/earnings";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
import shopIcon from "./assets/shop.svg";
|
import shopIcon from "./assets/shop.svg";
|
||||||
import apiaryIcon from "./assets/apiary.svg";
|
import apiaryIcon from "./assets/apiary.svg";
|
||||||
@@ -23,7 +23,7 @@ import rouletteIcon from "./assets/roulette.svg";
|
|||||||
import tasksIcon from "./assets/tasks.svg";
|
import tasksIcon from "./assets/tasks.svg";
|
||||||
import earningsIcon from "./assets/earnings.svg";
|
import earningsIcon from "./assets/earnings.svg";
|
||||||
import tg, { useTelegramViewportValue } from "@/tg";
|
import tg, { useTelegramViewportValue } from "@/tg";
|
||||||
import { usePlaySound } from "@/audio";
|
import { useAudioStore } from "@/audio";
|
||||||
|
|
||||||
const ANIMATION_DURATION = 0.2;
|
const ANIMATION_DURATION = 0.2;
|
||||||
const SPRING_ANIMATION = {
|
const SPRING_ANIMATION = {
|
||||||
@@ -51,6 +51,37 @@ const MENU_ITEMS = [
|
|||||||
{ key: "nav.earnings", route: EarningsRoute, icon: earningsIcon, delay: 0 },
|
{ key: "nav.earnings", route: EarningsRoute, icon: earningsIcon, delay: 0 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 200px is larger than any OS chrome resize but smaller than the smallest software keyboard
|
||||||
|
const KEYBOARD_THRESHOLD = 200;
|
||||||
|
|
||||||
|
function useKeyboardVisible(): boolean {
|
||||||
|
const [keyboardVisible, setKeyboardVisible] = useState(false);
|
||||||
|
const baselineHeight = useRef<number>(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const vv = window.visualViewport;
|
||||||
|
if (!vv) return;
|
||||||
|
|
||||||
|
baselineHeight.current = vv.height;
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
const newHeight = vv.height;
|
||||||
|
|
||||||
|
if (baselineHeight.current - newHeight > KEYBOARD_THRESHOLD) {
|
||||||
|
setKeyboardVisible(true);
|
||||||
|
} else {
|
||||||
|
setKeyboardVisible(false);
|
||||||
|
baselineHeight.current = newHeight;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vv.addEventListener("resize", handleResize);
|
||||||
|
return () => vv.removeEventListener("resize", handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return keyboardVisible;
|
||||||
|
}
|
||||||
|
|
||||||
type BarProps = {
|
type BarProps = {
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
@@ -162,11 +193,12 @@ function MenuBar({ labelKey, icon, delay, active, onClick }: MenuBarProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Navigation() {
|
export default function Navigation() {
|
||||||
const play = usePlaySound();
|
const play = useAudioStore((s) => s.play);
|
||||||
const matchRoute = useMatchRoute();
|
const matchRoute = useMatchRoute();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [menuOpen, setMenuOpen] = useState<number>(0);
|
const [menuOpen, setMenuOpen] = useState<number>(0);
|
||||||
const navRef = useRef<HTMLDivElement>(null);
|
const navRef = useRef<HTMLDivElement>(null);
|
||||||
|
const keyboardVisible = useKeyboardVisible();
|
||||||
|
|
||||||
const handleOutsideClick = useCallback((e: MouseEvent) => {
|
const handleOutsideClick = useCallback((e: MouseEvent) => {
|
||||||
if (navRef.current && !navRef.current.contains(e.target as Node)) {
|
if (navRef.current && !navRef.current.contains(e.target as Node)) {
|
||||||
@@ -193,7 +225,7 @@ export default function Navigation() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={navRef}>
|
<div ref={navRef} className={keyboardVisible ? classes.hidden : undefined}>
|
||||||
<div className={classes.menuOverlay}>
|
<div className={classes.menuOverlay}>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{menuOpen &&
|
{menuOpen &&
|
||||||
|
|||||||
@@ -29,9 +29,10 @@
|
|||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
background: #9ebf3e;
|
background: #9ebf3e;
|
||||||
height: var(--safe-area-height);
|
height: 100dvh;
|
||||||
width: var(--safe-area-width);
|
width: var(--safe-area-width);
|
||||||
font-family: "BalsamiqSans", sans-serif;
|
font-family: "BalsamiqSans", sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
|
|||||||
Reference in New Issue
Block a user