Redesign session UX: progress ring, round markers, red theme
Some checks failed
Build & Release APK / build (push) Has been cancelled
Some checks failed
Build & Release APK / build (push) Has been cancelled
- Animated ring sweeps the timer edge in proportion to the current step (react-native-svg), matching the meditation-timer idiom - Per-block round markers: circle = squeeze, square = push, X = quick flicks; small interleaved dots distinguish rest rounds from work rounds - Stamena-style red theme throughout (bg, accent, splash, adaptive icon) - Home screen legend teaching the shape vocabulary Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
6
app.json
6
app.json
@@ -7,13 +7,13 @@
|
|||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/images/icon.png",
|
||||||
"scheme": "kegel",
|
"scheme": "kegel",
|
||||||
"userInterfaceStyle": "dark",
|
"userInterfaceStyle": "dark",
|
||||||
"backgroundColor": "#0F1115",
|
"backgroundColor": "#160B0B",
|
||||||
"ios": {
|
"ios": {
|
||||||
"bundleIdentifier": "com.marcus.kegel"
|
"bundleIdentifier": "com.marcus.kegel"
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"backgroundColor": "#0F1115",
|
"backgroundColor": "#160B0B",
|
||||||
"foregroundImage": "./assets/images/android-icon-foreground.png",
|
"foregroundImage": "./assets/images/android-icon-foreground.png",
|
||||||
"monochromeImage": "./assets/images/android-icon-monochrome.png"
|
"monochromeImage": "./assets/images/android-icon-monochrome.png"
|
||||||
},
|
},
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
[
|
[
|
||||||
"expo-splash-screen",
|
"expo-splash-screen",
|
||||||
{
|
{
|
||||||
"backgroundColor": "#0F1115",
|
"backgroundColor": "#160B0B",
|
||||||
"image": "./assets/images/splash-icon.png",
|
"image": "./assets/images/splash-icon.png",
|
||||||
"imageWidth": 120
|
"imageWidth": 120
|
||||||
}
|
}
|
||||||
|
|||||||
198
package-lock.json
generated
198
package-lock.json
generated
@@ -23,6 +23,7 @@
|
|||||||
"react-native": "0.86.2",
|
"react-native": "0.86.2",
|
||||||
"react-native-safe-area-context": "~5.7.0",
|
"react-native-safe-area-context": "~5.7.0",
|
||||||
"react-native-screens": "~4.26.0",
|
"react-native-screens": "~4.26.0",
|
||||||
|
"react-native-svg": "15.15.4",
|
||||||
"react-native-web": "^0.21.2"
|
"react-native-web": "^0.21.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -64,7 +65,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
|
||||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.7",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/generator": "^7.29.7",
|
"@babel/generator": "^7.29.7",
|
||||||
@@ -561,6 +561,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz",
|
||||||
"integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==",
|
"integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.29.7"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
@@ -949,6 +950,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
|
||||||
"integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
|
"integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.29.7"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
@@ -964,6 +966,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
|
||||||
"integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
|
"integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.29.7"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
@@ -995,6 +998,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz",
|
||||||
"integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==",
|
"integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.29.7"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
@@ -1039,6 +1043,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz",
|
||||||
"integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==",
|
"integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.29.7"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
@@ -1054,6 +1059,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz",
|
||||||
"integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==",
|
"integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.29.7"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
@@ -2085,6 +2091,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.86.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.86.2.tgz",
|
||||||
"integrity": "sha512-4XKEJ6jKW9lXMB1O5o47gBoGgolde1fbX13gLW/erlcn+1ky+MHHo5UjuM3RWGdPJHIvIzekDDumSUHhB9x5iQ==",
|
"integrity": "sha512-4XKEJ6jKW9lXMB1O5o47gBoGgolde1fbX13gLW/erlcn+1ky+MHHo5UjuM3RWGdPJHIvIzekDDumSUHhB9x5iQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.24.7",
|
"@babel/plugin-proposal-export-default-from": "^7.24.7",
|
||||||
@@ -2247,6 +2254,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.86.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.86.2.tgz",
|
||||||
"integrity": "sha512-mX1wgLErdb2hDgXJr9zM9SWLe+ZteZTFTwRWGOQ53yEJwc9DVSnxdTlAtVdMeOj2ntycKh0R8jYdat2Am43cwQ==",
|
"integrity": "sha512-mX1wgLErdb2hDgXJr9zM9SWLe+ZteZTFTwRWGOQ53yEJwc9DVSnxdTlAtVdMeOj2ntycKh0R8jYdat2Am43cwQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
"@react-native/babel-preset": "0.86.2",
|
"@react-native/babel-preset": "0.86.2",
|
||||||
@@ -2265,6 +2273,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.86.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.86.2.tgz",
|
||||||
"integrity": "sha512-hJno256j+MS0b3JD1aD3ouTGZVacKNVBuXL2atMQQ8BZ060vl1ptnZ83y569aDW+/rgFSOcqn6ydKeSz4uUKQQ==",
|
"integrity": "sha512-hJno256j+MS0b3JD1aD3ouTGZVacKNVBuXL2atMQQ8BZ060vl1ptnZ83y569aDW+/rgFSOcqn6ydKeSz4uUKQQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native/js-polyfills": "0.86.2",
|
"@react-native/js-polyfills": "0.86.2",
|
||||||
"@react-native/metro-babel-transformer": "0.86.2",
|
"@react-native/metro-babel-transformer": "0.86.2",
|
||||||
@@ -2315,6 +2324,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/code-frame": "^7.10.4",
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
@@ -2334,6 +2344,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
@@ -2346,6 +2357,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1",
|
"ansi-regex": "^5.0.1",
|
||||||
"ansi-styles": "^5.0.0",
|
"ansi-styles": "^5.0.0",
|
||||||
@@ -2359,7 +2371,8 @@
|
|||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@testing-library/jest-dom": {
|
"node_modules/@testing-library/jest-dom": {
|
||||||
"version": "6.9.1",
|
"version": "6.9.1",
|
||||||
@@ -2403,7 +2416,8 @@
|
|||||||
"version": "5.0.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/istanbul-lib-coverage": {
|
"node_modules/@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
@@ -2443,7 +2457,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
|
||||||
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -2453,6 +2466,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz",
|
||||||
"integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==",
|
"integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
@@ -2772,6 +2786,12 @@
|
|||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/bplist-creator": {
|
"node_modules/bplist-creator": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
|
||||||
@@ -2836,7 +2856,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.11.12",
|
"baseline-browser-mapping": "^2.11.12",
|
||||||
"caniuse-lite": "^1.0.30001809",
|
"caniuse-lite": "^1.0.30001809",
|
||||||
@@ -3201,6 +3220,56 @@
|
|||||||
"hyphenate-style-name": "^1.0.3"
|
"hyphenate-style-name": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-tree": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mdn-data": "2.0.14",
|
||||||
|
"source-map": "^0.6.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-tree/node_modules/source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css.escape": {
|
"node_modules/css.escape": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||||
@@ -3313,7 +3382,63 @@
|
|||||||
"version": "0.5.16",
|
"version": "0.5.16",
|
||||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -3342,6 +3467,18 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/error-stack-parser": {
|
"node_modules/error-stack-parser": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
|
||||||
@@ -3410,7 +3547,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo/-/expo-57.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/expo/-/expo-57.0.13.tgz",
|
||||||
"integrity": "sha512-oJnPSAcQYDIXTJowL9fm6SvAU5CU0P+u1WtWM9a/7uUmeCrkcVKoaGstwF+UY3GoM11S7FO4KKmRl1aU12CTKA==",
|
"integrity": "sha512-oJnPSAcQYDIXTJowL9fm6SvAU5CU0P+u1WtWM9a/7uUmeCrkcVKoaGstwF+UY3GoM11S7FO4KKmRl1aU12CTKA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.0",
|
"@babel/runtime": "^7.20.0",
|
||||||
"@expo/cli": "^57.0.15",
|
"@expo/cli": "^57.0.15",
|
||||||
@@ -3473,7 +3609,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.11.tgz",
|
||||||
"integrity": "sha512-gpSZ13Nfx0BOc9ALjG1SMhks5csb8Z6ncEVdhDgv0NRrmJcHPRvyUlWaKjRjzwW730XrQ6PIXFRcnNmAxOLj5A==",
|
"integrity": "sha512-gpSZ13Nfx0BOc9ALjG1SMhks5csb8Z6ncEVdhDgv0NRrmJcHPRvyUlWaKjRjzwW730XrQ6PIXFRcnNmAxOLj5A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/env": "~2.4.2"
|
"@expo/env": "~2.4.2"
|
||||||
},
|
},
|
||||||
@@ -3487,7 +3622,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.1.tgz",
|
||||||
"integrity": "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==",
|
"integrity": "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fontfaceobserver": "^2.1.0"
|
"fontfaceobserver": "^2.1.0"
|
||||||
},
|
},
|
||||||
@@ -3532,7 +3666,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.6.tgz",
|
||||||
"integrity": "sha512-NdKjEGk+ywwGRNvgQCq9KettM1eVHfR1+uLov+0ohb6rmmMEgnstfLcc3f0DKMQ0PnROsY5G5+ZkNeiairYRXA==",
|
"integrity": "sha512-NdKjEGk+ywwGRNvgQCq9KettM1eVHfR1+uLov+0ohb6rmmMEgnstfLcc3f0DKMQ0PnROsY5G5+ZkNeiairYRXA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"expo-constants": "~57.0.11",
|
"expo-constants": "~57.0.11",
|
||||||
"invariant": "^2.2.4"
|
"invariant": "^2.2.4"
|
||||||
@@ -3750,7 +3883,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.3.tgz",
|
||||||
"integrity": "sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==",
|
"integrity": "sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.16.0"
|
"node": ">=20.16.0"
|
||||||
}
|
}
|
||||||
@@ -4148,7 +4280,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
|
||||||
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
|
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@@ -5197,6 +5328,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"lz-string": "bin/bin.js"
|
"lz-string": "bin/bin.js"
|
||||||
}
|
}
|
||||||
@@ -5216,6 +5348,12 @@
|
|||||||
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
|
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/mdn-data": {
|
||||||
|
"version": "2.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||||
|
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
},
|
||||||
"node_modules/memoize-one": {
|
"node_modules/memoize-one": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||||
@@ -5740,6 +5878,18 @@
|
|||||||
"node": "^16.14.0 || >=18.0.0"
|
"node": "^16.14.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nullthrows": {
|
"node_modules/nullthrows": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
|
||||||
@@ -6183,7 +6333,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -6203,7 +6352,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -6240,7 +6388,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.2.tgz",
|
||||||
"integrity": "sha512-zbJXGZpwfZGA79Z9ob6Atvfx4nAQL8yJBa35s58E4Oo+khPykfQP2sTeumkKbjwajFYfVayg8pj7Il9nIfTk7A==",
|
"integrity": "sha512-zbJXGZpwfZGA79Z9ob6Atvfx4nAQL8yJBa35s58E4Oo+khPykfQP2sTeumkKbjwajFYfVayg8pj7Il9nIfTk7A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native/assets-registry": "0.86.2",
|
"@react-native/assets-registry": "0.86.2",
|
||||||
"@react-native/codegen": "0.86.2",
|
"@react-native/codegen": "0.86.2",
|
||||||
@@ -6331,6 +6478,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz",
|
||||||
"integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==",
|
"integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
@@ -6357,7 +6505,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz",
|
||||||
"integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==",
|
"integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
@@ -6377,12 +6524,26 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-svg": {
|
||||||
|
"version": "15.15.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.4.tgz",
|
||||||
|
"integrity": "sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"css-tree": "^1.1.3",
|
||||||
|
"warn-once": "0.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-web": {
|
"node_modules/react-native-web": {
|
||||||
"version": "0.21.2",
|
"version": "0.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
||||||
"integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==",
|
"integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.18.6",
|
"@babel/runtime": "^7.18.6",
|
||||||
"@react-native/normalize-colors": "^0.74.1",
|
"@react-native/normalize-colors": "^0.74.1",
|
||||||
@@ -6451,7 +6612,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
|
||||||
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
|
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -7191,7 +7351,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
||||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -7259,7 +7418,6 @@
|
|||||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"react-native": "0.86.2",
|
"react-native": "0.86.2",
|
||||||
"react-native-safe-area-context": "~5.7.0",
|
"react-native-safe-area-context": "~5.7.0",
|
||||||
"react-native-screens": "~4.26.0",
|
"react-native-screens": "~4.26.0",
|
||||||
|
"react-native-svg": "15.15.4",
|
||||||
"react-native-web": "^0.21.2"
|
"react-native-web": "^0.21.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { StyleSheet, Text, View } from "react-native";
|
import { StyleSheet, Text, View } from "react-native";
|
||||||
|
import { ShapeIcon } from "../components/session-viz";
|
||||||
import { Button, StatRow } from "../components/ui";
|
import { Button, StatRow } from "../components/ui";
|
||||||
import { currentHold } from "../lib/store";
|
import { currentHold } from "../lib/store";
|
||||||
import { C } from "../lib/theme";
|
import { C } from "../lib/theme";
|
||||||
import { useStore } from "../lib/useStore";
|
import { useStore } from "../lib/useStore";
|
||||||
import { MAX_HOLD, SESSIONS_PER_INCREMENT, sessionMinutes } from "../lib/workout";
|
import { FLICK_REPS, HOLD_REPS, MAX_HOLD, PUSH_REPS, SESSIONS_PER_INCREMENT, sessionMinutes } from "../lib/workout";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -31,6 +32,21 @@ export default function Home() {
|
|||||||
<StatRow label="Last session" value={lastSessionAt ? new Date(lastSessionAt).toLocaleDateString() : "—"} />
|
<StatRow label="Last session" value={lastSessionAt ? new Date(lastSessionAt).toLocaleDateString() : "—"} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={s.card}>
|
||||||
|
<View style={s.legendRow}>
|
||||||
|
<ShapeIcon shape="circle" size={12} color={C.accent} />
|
||||||
|
<Text style={s.legendText}>Squeeze & hold — {HOLD_REPS} reps</Text>
|
||||||
|
</View>
|
||||||
|
<View style={s.legendRow}>
|
||||||
|
<ShapeIcon shape="square" size={12} color={C.push} />
|
||||||
|
<Text style={s.legendText}>Push out — {PUSH_REPS} reps</Text>
|
||||||
|
</View>
|
||||||
|
<View style={[s.legendRow, s.legendLast]}>
|
||||||
|
<ShapeIcon shape="x" size={12} color={C.accent} />
|
||||||
|
<Text style={s.legendText}>Quick squeezes & pushes — {FLICK_REPS} each</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<Button title="Start session" onPress={() => router.push("/session")} />
|
<Button title="Start session" onPress={() => router.push("/session")} />
|
||||||
|
|
||||||
<Text style={s.hint}>
|
<Text style={s.hint}>
|
||||||
@@ -55,5 +71,15 @@ const s = StyleSheet.create({
|
|||||||
paddingVertical: 4,
|
paddingVertical: 4,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
|
legendRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: C.border,
|
||||||
|
},
|
||||||
|
legendLast: { borderBottomWidth: 0 },
|
||||||
|
legendText: { color: C.text, fontSize: 14 },
|
||||||
hint: { color: C.muted, fontSize: 13, lineHeight: 19, marginTop: 8 },
|
hint: { color: C.muted, fontSize: 13, lineHeight: 19, marginTop: 8 },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useRouter } from "expo-router";
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { StyleSheet, Text, View } from "react-native";
|
import { StyleSheet, Text, View } from "react-native";
|
||||||
import { SafeAreaView } from "react-native-safe-area-context";
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
|
import { BLOCK_SHAPE, ProgressRing, RoundDots, ShapeIcon, useStepProgress } from "../components/session-viz";
|
||||||
import { Button } from "../components/ui";
|
import { Button } from "../components/ui";
|
||||||
import { completeSession, currentHold, getState } from "../lib/store";
|
import { completeSession, currentHold, getState } from "../lib/store";
|
||||||
import { C } from "../lib/theme";
|
import { C } from "../lib/theme";
|
||||||
@@ -15,7 +16,7 @@ const KIND_COLOR: Record<StepKind, string> = {
|
|||||||
push: C.push,
|
push: C.push,
|
||||||
"flick-squeeze": C.accent,
|
"flick-squeeze": C.accent,
|
||||||
"flick-push": C.push,
|
"flick-push": C.push,
|
||||||
relax: C.muted,
|
relax: C.rest,
|
||||||
rest: C.rest,
|
rest: C.rest,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ export default function Session() {
|
|||||||
const [done, setDone] = useState(false);
|
const [done, setDone] = useState(false);
|
||||||
const recorded = useRef(false);
|
const recorded = useRef(false);
|
||||||
|
|
||||||
|
const progress = useStepProgress(i, remaining, paused, done);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (paused || done) return;
|
if (paused || done) return;
|
||||||
const id = setInterval(() => setRemaining((r) => r - 1), 1000);
|
const id = setInterval(() => setRemaining((r) => r - 1), 1000);
|
||||||
@@ -83,17 +86,22 @@ export default function Session() {
|
|||||||
const step = steps[i];
|
const step = steps[i];
|
||||||
const elapsed = steps.slice(0, i).reduce((s, st) => s + st.seconds, 0) + (step.seconds - remaining);
|
const elapsed = steps.slice(0, i).reduce((s, st) => s + st.seconds, 0) + (step.seconds - remaining);
|
||||||
const color = KIND_COLOR[step.kind];
|
const color = KIND_COLOR[step.kind];
|
||||||
|
const shape = BLOCK_SHAPE[step.blockId];
|
||||||
const next = steps[i + 1];
|
const next = steps[i + 1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={s.screen}>
|
<SafeAreaView style={s.screen}>
|
||||||
<Text style={s.block}>{step.block}</Text>
|
<Text style={s.block}>{step.block}</Text>
|
||||||
|
<RoundDots step={step} />
|
||||||
|
|
||||||
<View style={s.center}>
|
<View style={s.center}>
|
||||||
<View style={[s.circle, { borderColor: color }]}>
|
<ProgressRing size={300} strokeWidth={10} progress={progress} color={color}>
|
||||||
|
<View style={s.inner}>
|
||||||
|
{shape ? <ShapeIcon shape={shape} size={22} color={color} /> : null}
|
||||||
<Text style={[s.label, { color }]}>{step.label}</Text>
|
<Text style={[s.label, { color }]}>{step.label}</Text>
|
||||||
<Text style={s.count}>{remaining}</Text>
|
<Text style={s.count}>{remaining}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
</ProgressRing>
|
||||||
<Text style={s.next}>{next ? `Next: ${next.label} ${next.seconds}s` : "Last one!"}</Text>
|
<Text style={s.next}>{next ? `Next: ${next.label} ${next.seconds}s` : "Last one!"}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -109,18 +117,10 @@ export default function Session() {
|
|||||||
|
|
||||||
const s = StyleSheet.create({
|
const s = StyleSheet.create({
|
||||||
screen: { flex: 1, backgroundColor: C.bg, padding: 20 },
|
screen: { flex: 1, backgroundColor: C.bg, padding: 20 },
|
||||||
block: { color: C.muted, fontSize: 15, textAlign: "center", textTransform: "uppercase", letterSpacing: 1, marginTop: 8 },
|
block: { color: C.muted, fontSize: 15, textAlign: "center", textTransform: "uppercase", letterSpacing: 1, marginTop: 8, marginBottom: 12 },
|
||||||
center: { flex: 1, alignItems: "center", justifyContent: "center" },
|
center: { flex: 1, alignItems: "center", justifyContent: "center" },
|
||||||
circle: {
|
inner: { alignItems: "center", justifyContent: "center", gap: 6 },
|
||||||
width: 260,
|
label: { fontSize: 22, fontWeight: "600" },
|
||||||
height: 260,
|
|
||||||
borderRadius: 130,
|
|
||||||
borderWidth: 6,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: C.surface,
|
|
||||||
},
|
|
||||||
label: { fontSize: 22, fontWeight: "600", marginBottom: 4 },
|
|
||||||
count: { color: C.text, fontSize: 80, fontWeight: "700", fontVariant: ["tabular-nums"] },
|
count: { color: C.text, fontSize: 80, fontWeight: "700", fontVariant: ["tabular-nums"] },
|
||||||
next: { color: C.muted, fontSize: 15, marginTop: 24 },
|
next: { color: C.muted, fontSize: 15, marginTop: 24 },
|
||||||
doneTitle: { color: C.text, fontSize: 28, fontWeight: "700", marginBottom: 12 },
|
doneTitle: { color: C.text, fontSize: 28, fontWeight: "700", marginBottom: 12 },
|
||||||
|
|||||||
166
src/components/session-viz.tsx
Normal file
166
src/components/session-viz.tsx
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { ReactNode, useEffect, useRef } from "react";
|
||||||
|
import { Animated, Easing, StyleSheet, View } from "react-native";
|
||||||
|
import Svg, { Circle } from "react-native-svg";
|
||||||
|
import { C } from "../lib/theme";
|
||||||
|
import type { BlockId, Step } from "../lib/workout";
|
||||||
|
|
||||||
|
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
||||||
|
|
||||||
|
/** Shape language: ○ = squeeze (in), □ = push (out), ✕ = quick flicks. */
|
||||||
|
export type Shape = "circle" | "square" | "x";
|
||||||
|
|
||||||
|
export const BLOCK_SHAPE: Record<BlockId, Shape | null> = {
|
||||||
|
warmup: null,
|
||||||
|
holds: "circle",
|
||||||
|
pushes: "square",
|
||||||
|
"flick-squeezes": "x",
|
||||||
|
"flick-pushes": "x",
|
||||||
|
};
|
||||||
|
|
||||||
|
const BLOCK_COLOR: Record<BlockId, string> = {
|
||||||
|
warmup: C.muted,
|
||||||
|
holds: C.accent,
|
||||||
|
pushes: C.push,
|
||||||
|
"flick-squeezes": C.accent,
|
||||||
|
"flick-pushes": C.push,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ShapeIcon({ shape, size, color, outline }: { shape: Shape; size: number; color: string; outline?: boolean }) {
|
||||||
|
if (shape === "x") {
|
||||||
|
const bar = { position: "absolute" as const, width: size * 1.25, height: Math.max(2, size * 0.26), borderRadius: size, backgroundColor: color };
|
||||||
|
return (
|
||||||
|
<View style={{ width: size, height: size, alignItems: "center", justifyContent: "center" }}>
|
||||||
|
<View style={[bar, { transform: [{ rotate: "45deg" }] }]} />
|
||||||
|
<View style={[bar, { transform: [{ rotate: "-45deg" }] }]} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
borderRadius: shape === "circle" ? size / 2 : Math.max(2, size * 0.18),
|
||||||
|
backgroundColor: outline ? "transparent" : color,
|
||||||
|
borderWidth: outline ? 1.5 : 0,
|
||||||
|
borderColor: color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ring that sweeps around the timer edge in proportion to the current step —
|
||||||
|
* same idiom as the meditation app. `progress` runs 0→1 over the step.
|
||||||
|
*/
|
||||||
|
export function ProgressRing({
|
||||||
|
size,
|
||||||
|
strokeWidth,
|
||||||
|
progress,
|
||||||
|
color,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
size: number;
|
||||||
|
strokeWidth: number;
|
||||||
|
progress: Animated.Value;
|
||||||
|
color: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}) {
|
||||||
|
const r = (size - strokeWidth) / 2;
|
||||||
|
const circumference = 2 * Math.PI * r;
|
||||||
|
return (
|
||||||
|
<View style={{ width: size, height: size, alignItems: "center", justifyContent: "center" }}>
|
||||||
|
<Svg width={size} height={size} style={StyleSheet.absoluteFill}>
|
||||||
|
<Circle cx={size / 2} cy={size / 2} r={r} stroke={C.surface2} strokeWidth={strokeWidth} fill={C.surface} />
|
||||||
|
<AnimatedCircle
|
||||||
|
cx={size / 2}
|
||||||
|
cy={size / 2}
|
||||||
|
r={r}
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
strokeDasharray={`${circumference}`}
|
||||||
|
strokeDashoffset={progress.interpolate({ inputRange: [0, 1], outputRange: [circumference, 0] })}
|
||||||
|
rotation={-90}
|
||||||
|
origin={`${size / 2}, ${size / 2}`}
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Drives a ProgressRing: restarts on each step, freezes on pause. */
|
||||||
|
export function useStepProgress(stepIndex: number, remaining: number, paused: boolean, done: boolean) {
|
||||||
|
const progress = useRef(new Animated.Value(0)).current;
|
||||||
|
const lastStep = useRef(-1);
|
||||||
|
const remainingRef = useRef(remaining);
|
||||||
|
remainingRef.current = remaining;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (done) return;
|
||||||
|
if (lastStep.current !== stepIndex) {
|
||||||
|
lastStep.current = stepIndex;
|
||||||
|
progress.setValue(0);
|
||||||
|
}
|
||||||
|
if (paused) return;
|
||||||
|
const anim = Animated.timing(progress, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: remainingRef.current * 1000,
|
||||||
|
easing: Easing.linear,
|
||||||
|
useNativeDriver: false,
|
||||||
|
});
|
||||||
|
anim.start();
|
||||||
|
return () => anim.stop();
|
||||||
|
}, [stepIndex, paused, done, progress]);
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where the round is out of the block: one marker per rep in the block's own
|
||||||
|
* shape, with small interleaved dots for the rests between hold/push reps so
|
||||||
|
* rest rounds read differently from work rounds. Flick blocks skip the 1s
|
||||||
|
* relax gaps — 10 markers is already the ceiling of glanceable.
|
||||||
|
*/
|
||||||
|
export function RoundDots({ step }: { step: Step }) {
|
||||||
|
const shape = BLOCK_SHAPE[step.blockId];
|
||||||
|
if (!shape) return <View style={d.row} />;
|
||||||
|
|
||||||
|
const color = BLOCK_COLOR[step.blockId];
|
||||||
|
const showRests = step.blockId === "holds" || step.blockId === "pushes";
|
||||||
|
const working = step.kind !== "rest" && step.kind !== "relax";
|
||||||
|
const markers: ReactNode[] = [];
|
||||||
|
|
||||||
|
for (let r = 1; r <= step.repsTotal; r++) {
|
||||||
|
const isCurrent = r === step.rep && working;
|
||||||
|
const isDone = r < step.rep || (r === step.rep && !working);
|
||||||
|
markers.push(
|
||||||
|
<View key={`w${r}`} style={isCurrent ? d.current : undefined}>
|
||||||
|
<ShapeIcon shape={shape} size={isCurrent ? 15 : 11} color={isDone || isCurrent ? color : C.border} outline={!isDone && !isCurrent} />
|
||||||
|
</View>,
|
||||||
|
);
|
||||||
|
if (showRests && r < step.repsTotal) {
|
||||||
|
const restCurrent = r === step.rep && !working;
|
||||||
|
markers.push(
|
||||||
|
<View
|
||||||
|
key={`r${r}`}
|
||||||
|
style={{
|
||||||
|
width: restCurrent ? 7 : 4,
|
||||||
|
height: restCurrent ? 7 : 4,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: r < step.rep ? C.muted : restCurrent ? C.rest : C.border,
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <View style={d.row}>{markers}</View>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = StyleSheet.create({
|
||||||
|
row: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 7, minHeight: 20 },
|
||||||
|
current: { transform: [{ scale: 1.15 }] },
|
||||||
|
});
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
|
// Stamena-style red theme: near-black red-tinted ground, saturated red as the
|
||||||
|
// primary work color, warm amber for reverse-kegel pushes so the two moves
|
||||||
|
// never read as the same cue.
|
||||||
export const C = {
|
export const C = {
|
||||||
bg: "#0F1115",
|
bg: "#160B0B",
|
||||||
surface: "#1A1D24",
|
surface: "#241213",
|
||||||
surface2: "#232733",
|
surface2: "#2F1718",
|
||||||
border: "#2C313D",
|
border: "#42201F",
|
||||||
text: "#E6E8EC",
|
text: "#F7ECEA",
|
||||||
muted: "#8A909C",
|
muted: "#A98A86",
|
||||||
accent: "#8B5CF6", // squeeze / primary
|
accent: "#E7402E", // squeeze / primary
|
||||||
push: "#F59E0B", // reverse kegel (push out)
|
push: "#FF9D3B", // reverse kegel (push out)
|
||||||
rest: "#4CAF7D",
|
rest: "#8B7573", // recovery — deliberately dull so work steps pop
|
||||||
danger: "#E05B5B",
|
danger: "#FF5D5D",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,12 +10,18 @@ export type StepKind =
|
|||||||
| "relax" // 1s gap between flicks
|
| "relax" // 1s gap between flicks
|
||||||
| "rest"; // recovery between reps/blocks
|
| "rest"; // recovery between reps/blocks
|
||||||
|
|
||||||
|
export type BlockId = "warmup" | "holds" | "pushes" | "flick-squeezes" | "flick-pushes";
|
||||||
|
|
||||||
export interface Step {
|
export interface Step {
|
||||||
kind: StepKind;
|
kind: StepKind;
|
||||||
label: string;
|
label: string;
|
||||||
seconds: number;
|
seconds: number;
|
||||||
/** Block title shown as secondary context, e.g. "Holds 3/8". */
|
/** Block title shown as secondary context, e.g. "Holds 3/8". */
|
||||||
block: string;
|
block: string;
|
||||||
|
blockId: BlockId;
|
||||||
|
/** Which rep of the block this step belongs to (rests carry the rep they follow). */
|
||||||
|
rep: number;
|
||||||
|
repsTotal: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MIN_HOLD = 5;
|
export const MIN_HOLD = 5;
|
||||||
@@ -34,9 +40,9 @@ function pushSeconds(hold: number): number {
|
|||||||
return Math.min(12, MIN_HOLD + Math.floor((hold - MIN_HOLD) / 2));
|
return Math.min(12, MIN_HOLD + Math.floor((hold - MIN_HOLD) / 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
const HOLD_REPS = 8;
|
export const HOLD_REPS = 8;
|
||||||
const PUSH_REPS = 5;
|
export const PUSH_REPS = 5;
|
||||||
const FLICK_REPS = 10;
|
export const FLICK_REPS = 10;
|
||||||
const BLOCK_REST = 20;
|
const BLOCK_REST = 20;
|
||||||
|
|
||||||
export function buildSession(hold: number): Step[] {
|
export function buildSession(hold: number): Step[] {
|
||||||
@@ -44,33 +50,33 @@ export function buildSession(hold: number): Step[] {
|
|||||||
const repRest = Math.max(4, Math.round(hold * 0.75));
|
const repRest = Math.max(4, Math.round(hold * 0.75));
|
||||||
const push = pushSeconds(hold);
|
const push = pushSeconds(hold);
|
||||||
|
|
||||||
steps.push({ kind: "ready", label: "Get ready", seconds: 5, block: "Warm up" });
|
steps.push({ kind: "ready", label: "Get ready", seconds: 5, block: "Warm up", blockId: "warmup", rep: 0, repsTotal: 0 });
|
||||||
|
|
||||||
// Block 1: squeeze-and-hold (the progressive-overload driver).
|
// Block 1: squeeze-and-hold (the progressive-overload driver).
|
||||||
for (let r = 1; r <= HOLD_REPS; r++) {
|
for (let r = 1; r <= HOLD_REPS; r++) {
|
||||||
steps.push({ kind: "squeeze", label: "Squeeze & hold", seconds: hold, block: `Holds ${r}/${HOLD_REPS}` });
|
steps.push({ kind: "squeeze", label: "Squeeze & hold", seconds: hold, block: `Holds ${r}/${HOLD_REPS}`, blockId: "holds", rep: r, repsTotal: HOLD_REPS });
|
||||||
if (r < HOLD_REPS) steps.push({ kind: "rest", label: "Rest", seconds: repRest, block: `Holds ${r}/${HOLD_REPS}` });
|
if (r < HOLD_REPS) steps.push({ kind: "rest", label: "Rest", seconds: repRest, block: `Holds ${r}/${HOLD_REPS}`, blockId: "holds", rep: r, repsTotal: HOLD_REPS });
|
||||||
}
|
}
|
||||||
steps.push({ kind: "rest", label: "Rest", seconds: BLOCK_REST, block: "Holds done" });
|
steps.push({ kind: "rest", label: "Rest", seconds: BLOCK_REST, block: "Holds done", blockId: "holds", rep: HOLD_REPS, repsTotal: HOLD_REPS });
|
||||||
|
|
||||||
// Block 2: reverse kegels — gently push out, as when starting to urinate.
|
// Block 2: reverse kegels — gently push out, as when starting to urinate.
|
||||||
for (let r = 1; r <= PUSH_REPS; r++) {
|
for (let r = 1; r <= PUSH_REPS; r++) {
|
||||||
steps.push({ kind: "push", label: "Push out", seconds: push, block: `Pushes ${r}/${PUSH_REPS}` });
|
steps.push({ kind: "push", label: "Push out", seconds: push, block: `Pushes ${r}/${PUSH_REPS}`, blockId: "pushes", rep: r, repsTotal: PUSH_REPS });
|
||||||
if (r < PUSH_REPS) steps.push({ kind: "rest", label: "Rest", seconds: 5, block: `Pushes ${r}/${PUSH_REPS}` });
|
if (r < PUSH_REPS) steps.push({ kind: "rest", label: "Rest", seconds: 5, block: `Pushes ${r}/${PUSH_REPS}`, blockId: "pushes", rep: r, repsTotal: PUSH_REPS });
|
||||||
}
|
}
|
||||||
steps.push({ kind: "rest", label: "Rest", seconds: BLOCK_REST, block: "Pushes done" });
|
steps.push({ kind: "rest", label: "Rest", seconds: BLOCK_REST, block: "Pushes done", blockId: "pushes", rep: PUSH_REPS, repsTotal: PUSH_REPS });
|
||||||
|
|
||||||
// Block 3: quick flicks in — fast full contractions, full release between.
|
// Block 3: quick flicks in — fast full contractions, full release between.
|
||||||
for (let r = 1; r <= FLICK_REPS; r++) {
|
for (let r = 1; r <= FLICK_REPS; r++) {
|
||||||
steps.push({ kind: "flick-squeeze", label: "Squeeze", seconds: 1, block: `Quick squeezes ${r}/${FLICK_REPS}` });
|
steps.push({ kind: "flick-squeeze", label: "Squeeze", seconds: 1, block: `Quick squeezes ${r}/${FLICK_REPS}`, blockId: "flick-squeezes", rep: r, repsTotal: FLICK_REPS });
|
||||||
steps.push({ kind: "relax", label: "Relax", seconds: 1, block: `Quick squeezes ${r}/${FLICK_REPS}` });
|
steps.push({ kind: "relax", label: "Relax", seconds: 1, block: `Quick squeezes ${r}/${FLICK_REPS}`, blockId: "flick-squeezes", rep: r, repsTotal: FLICK_REPS });
|
||||||
}
|
}
|
||||||
steps.push({ kind: "rest", label: "Rest", seconds: 15, block: "Quick squeezes done" });
|
steps.push({ kind: "rest", label: "Rest", seconds: 15, block: "Quick squeezes done", blockId: "flick-squeezes", rep: FLICK_REPS, repsTotal: FLICK_REPS });
|
||||||
|
|
||||||
// Block 4: quick flicks out.
|
// Block 4: quick flicks out.
|
||||||
for (let r = 1; r <= FLICK_REPS; r++) {
|
for (let r = 1; r <= FLICK_REPS; r++) {
|
||||||
steps.push({ kind: "flick-push", label: "Push", seconds: 1, block: `Quick pushes ${r}/${FLICK_REPS}` });
|
steps.push({ kind: "flick-push", label: "Push", seconds: 1, block: `Quick pushes ${r}/${FLICK_REPS}`, blockId: "flick-pushes", rep: r, repsTotal: FLICK_REPS });
|
||||||
steps.push({ kind: "relax", label: "Relax", seconds: 1, block: `Quick pushes ${r}/${FLICK_REPS}` });
|
steps.push({ kind: "relax", label: "Relax", seconds: 1, block: `Quick pushes ${r}/${FLICK_REPS}`, blockId: "flick-pushes", rep: r, repsTotal: FLICK_REPS });
|
||||||
}
|
}
|
||||||
|
|
||||||
return steps;
|
return steps;
|
||||||
|
|||||||
Reference in New Issue
Block a user