mirror of
https://github.com/remvze/moodist.git
synced 2026-03-05 19:43:13 +08:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73a8e03d66 | ||
|
|
017c27fb2b | ||
|
|
43ba975408 | ||
|
|
42bd47bbea | ||
|
|
faf7f78b8c | ||
|
|
7ec7ea74d5 | ||
|
|
ede480186c | ||
|
|
6dfa998ffe | ||
|
|
a8a8c36434 | ||
|
|
7390a9b3de | ||
|
|
0eb47ba2e1 | ||
|
|
110356b2da | ||
|
|
158cffca8c | ||
|
|
def69de6e4 | ||
|
|
69cb45bff7 | ||
|
|
e7fd84bd4e | ||
|
|
dfd6c1fc4a | ||
|
|
38a9a23790 | ||
|
|
2484e01273 | ||
|
|
98d2f76438 | ||
|
|
0e12a5203e | ||
|
|
3205145d54 | ||
|
|
8f36c863d7 | ||
|
|
941e1f0241 | ||
|
|
240fd9c6e0 | ||
|
|
665e2173f4 | ||
|
|
1ac52861d1 | ||
|
|
a7e5368591 | ||
|
|
f3cb2a1b63 | ||
|
|
586e502c3c | ||
|
|
5b83710c75 | ||
|
|
7ed016d855 | ||
|
|
9f7de336e5 | ||
|
|
758f2f48dc | ||
|
|
d055e66dd9 | ||
|
|
79afb8d92f | ||
|
|
408734d49f | ||
|
|
7463334053 | ||
|
|
ae3ea8c74f | ||
|
|
24245235b1 | ||
|
|
b143e46e92 | ||
|
|
e923559709 | ||
|
|
8930e7b76a | ||
|
|
5f40435c0c | ||
|
|
38c11f124e | ||
|
|
a5d2ba45f8 | ||
|
|
653d309e64 | ||
|
|
663cb92135 | ||
|
|
1a499be244 | ||
|
|
4515aa8e7a | ||
|
|
831a9c8ea0 | ||
|
|
f66a6ffde7 | ||
|
|
9028675057 | ||
|
|
37505a6b3f | ||
|
|
400ea0aeaf | ||
|
|
e4e332ad75 | ||
|
|
341a896924 | ||
|
|
1a6ecd82ab | ||
|
|
d725d59703 | ||
|
|
52176bc3f9 | ||
|
|
c505c574a8 | ||
|
|
3f45be3942 | ||
|
|
a514a364ec | ||
|
|
81e6666776 | ||
|
|
56802b67f2 | ||
|
|
d4cc24e468 | ||
|
|
f7302dec5b | ||
|
|
1a01a00866 | ||
|
|
38da02a0d3 | ||
|
|
5fc3e7e5d0 | ||
|
|
0f32de3c0c | ||
|
|
ac24da2940 | ||
|
|
5916e86d3c | ||
|
|
f3e7224267 | ||
|
|
e1b9a1736c | ||
|
|
2078648c66 | ||
|
|
6d30a0123e | ||
|
|
8471a3ca49 | ||
|
|
182a8c7aad | ||
|
|
0ad4bb72e1 | ||
|
|
405fcccd95 | ||
|
|
2b843747b4 | ||
|
|
8e500136ce | ||
|
|
882d44079c | ||
|
|
82e4ea72f4 | ||
|
|
dc22b51548 | ||
|
|
75ff67c9e6 | ||
|
|
1f806c4e56 | ||
|
|
e6f768a5e6 | ||
|
|
8e0291004a | ||
|
|
d449c29321 | ||
|
|
f12ca4806c | ||
|
|
17b4f25ff1 | ||
|
|
f877e49763 | ||
|
|
f1d212abc8 | ||
|
|
38f6f7dbe6 | ||
|
|
937bf29d09 | ||
|
|
e2172fd2bb | ||
|
|
1f12afa394 | ||
|
|
d96461d1ea | ||
|
|
5467bbbc24 | ||
|
|
32da26ccfc | ||
|
|
81f33d9d37 | ||
|
|
463667c868 | ||
|
|
b77c817db2 | ||
|
|
216b913ccd | ||
|
|
e422b52436 | ||
|
|
1f635348e3 | ||
|
|
889962babe | ||
|
|
5e0a84259f | ||
|
|
8e4d0531e0 | ||
|
|
cd05704a73 | ||
|
|
01b4bdbb57 | ||
|
|
f682a910da | ||
|
|
a33ae450cf |
@@ -45,6 +45,7 @@
|
||||
"sort-keys-fix/sort-keys-fix": ["warn", "asc"],
|
||||
"sort-destructure-keys/sort-destructure-keys": "warn",
|
||||
"jsx-a11y/no-static-element-interactions": "off",
|
||||
"jsx-a11y/media-has-caption": "off",
|
||||
"react/jsx-sort-props": [
|
||||
"warn",
|
||||
{
|
||||
|
||||
18
.github/workflows/build_docker.yml
vendored
18
.github/workflows/build_docker.yml
vendored
@@ -20,15 +20,21 @@ jobs:
|
||||
username: ${{github.actor}}
|
||||
password: ${{secrets.ACCESS_TOKEN}}
|
||||
|
||||
- name: 'Build Inventory Image'
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: 'Build and push Inventory Image'
|
||||
run: |
|
||||
IMAGE_NAME="ghcr.io/remvze/moodist"
|
||||
|
||||
GIT_TAG=${{ github.ref }}
|
||||
GIT_TAG=${GIT_TAG#refs/tags/}
|
||||
|
||||
docker build . --tag $IMAGE_NAME:latest
|
||||
docker push $IMAGE_NAME:latest
|
||||
|
||||
docker build . --tag $IMAGE_NAME:$GIT_TAG
|
||||
docker push $IMAGE_NAME:$GIT_TAG
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64,linux/arm/v7 \
|
||||
-t $IMAGE_NAME:latest \
|
||||
-t $IMAGE_NAME:$GIT_TAG \
|
||||
--push .
|
||||
|
||||
182
CHANGELOG.md
182
CHANGELOG.md
@@ -2,6 +2,188 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [1.4.2](https://github.com/remvze/moodist/compare/v1.4.1...v1.4.2) (2024-04-11)
|
||||
|
||||
|
||||
### 💄 Styling
|
||||
|
||||
* change font path ([43ba975](https://github.com/remvze/moodist/commit/43ba9754085d7a710d3d629e70f873b16f267507))
|
||||
|
||||
|
||||
### 🚚 Chores
|
||||
|
||||
* remove arm/v6 ([017c27f](https://github.com/remvze/moodist/commit/017c27fb2b20402553011db8f417717dcca6d447))
|
||||
|
||||
### [1.4.1](https://github.com/remvze/moodist/compare/v1.4.0...v1.4.1) (2024-04-11)
|
||||
|
||||
|
||||
### ✨ Features
|
||||
|
||||
* add toolbar and portal ([ede4801](https://github.com/remvze/moodist/commit/ede480186c4b3f187c82e1d64e4d521ee59da31a))
|
||||
|
||||
|
||||
### 💄 Styling
|
||||
|
||||
* widen the container ([7ec7ea7](https://github.com/remvze/moodist/commit/7ec7ea74d53db85cffa3af646c03270793453009))
|
||||
|
||||
|
||||
### 🚚 Chores
|
||||
|
||||
* change GitHub workflow ([faf7f78](https://github.com/remvze/moodist/commit/faf7f78b8c10cd7b3688ed5bba3d0c077c020ad2))
|
||||
|
||||
## [1.4.0](https://github.com/remvze/moodist/compare/v1.3.1...v1.4.0) (2024-02-29)
|
||||
|
||||
|
||||
### ♻️ Code Refactoring
|
||||
|
||||
* add variant to container ([831a9c8](https://github.com/remvze/moodist/commit/831a9c8ea019a3d86e994ff0e060dd4337a84d1f))
|
||||
* move donation to React ([c505c57](https://github.com/remvze/moodist/commit/c505c574a885004e071da63d8255062befc29921))
|
||||
* move footer to React ([52176bc](https://github.com/remvze/moodist/commit/52176bc3f9eac43d701de0e7f0ca103eeca46858))
|
||||
* remove sections ([3f45be3](https://github.com/remvze/moodist/commit/3f45be3942bfeff74f3f0126de5e61037a749e61))
|
||||
|
||||
|
||||
### 💄 Styling
|
||||
|
||||
* add effect to about ([1a499be](https://github.com/remvze/moodist/commit/1a499be2446730d5333dd0d0d6a06bbd47564979))
|
||||
* add margin to donate section ([6d30a01](https://github.com/remvze/moodist/commit/6d30a0123e0feb509b6c560f405b98d20a89467a))
|
||||
* add polka dot pattern ([dc22b51](https://github.com/remvze/moodist/commit/dc22b51548f0d6830fcee79eebd75650f3f19dc1))
|
||||
* add scroll lock in modals ([def69de](https://github.com/remvze/moodist/commit/def69de6e4e11e373280c90f93af0b0369b85ac8))
|
||||
* add shine to donation button ([ac24da2](https://github.com/remvze/moodist/commit/ac24da294008a34c49dc3502374f1fcf55db5be8))
|
||||
* change description ([8930e7b](https://github.com/remvze/moodist/commit/8930e7b76abffafd0ace5926de6c1d3f7629febd))
|
||||
* change dividers ([8471a3c](https://github.com/remvze/moodist/commit/8471a3ca493b0c738ed7de900e82403f0b1ce2b7))
|
||||
* change pattern ([f3e7224](https://github.com/remvze/moodist/commit/f3e72242670317d938cc8d9619729be95df0f4f0))
|
||||
* change position for toolbar ([e7fd84b](https://github.com/remvze/moodist/commit/e7fd84bd4e8637e34eb0a59e97fd9c49875f8776))
|
||||
* change sound counter ([e1b9a17](https://github.com/remvze/moodist/commit/e1b9a1736c1d11827faf900838769194364afbd3))
|
||||
* change the about style ([4515aa8](https://github.com/remvze/moodist/commit/4515aa8e7a7f6d0fbc839625f74f0583e1a20d18))
|
||||
* change the pattern slightly ([5fc3e7e](https://github.com/remvze/moodist/commit/5fc3e7e5d048cb4aa189683d147b181fdf2a94b6))
|
||||
* change unselected style ([586e502](https://github.com/remvze/moodist/commit/586e502c3cc81314bc1e83f4e088a0d9289390fc))
|
||||
* decorate paragraphs ([1a6ecd8](https://github.com/remvze/moodist/commit/1a6ecd82abe89e1686538c42b31826ccc8a43b2d))
|
||||
* decrease dots ([182a8c7](https://github.com/remvze/moodist/commit/182a8c7aadc9a253261c56ae7faf8ac5c3c82707))
|
||||
* decrease dots ([0ad4bb7](https://github.com/remvze/moodist/commit/0ad4bb72e15e8f7d52e7d4b036b71fdb837e3554))
|
||||
* decrease dots ([2b84374](https://github.com/remvze/moodist/commit/2b843747b41111908bbe5fb8f5abc407114e4f15))
|
||||
* decrease font size ([69cb45b](https://github.com/remvze/moodist/commit/69cb45bff74d36f654d521e9e7f6b2149b01d630))
|
||||
* decrease opacity ([56802b6](https://github.com/remvze/moodist/commit/56802b67f2db751dbede9aa58b2158dd250a1420))
|
||||
* decrease opacity ([2078648](https://github.com/remvze/moodist/commit/2078648c6687aab79a725490335b8ae751f3d4ee))
|
||||
* decrease opacity ([82e4ea7](https://github.com/remvze/moodist/commit/82e4ea72f4ddb8658824813a64e14970400b1820))
|
||||
* decrease padding ([98d2f76](https://github.com/remvze/moodist/commit/98d2f764383eaba0dd6163b93826459b614b67d2))
|
||||
* decrease shine ([0f32de3](https://github.com/remvze/moodist/commit/0f32de3c0ca9f553c8917b786ddcdfb6feccf0c8))
|
||||
* hide about and features ([400ea0a](https://github.com/remvze/moodist/commit/400ea0aeafe48587fe8596d1b5fe90755995d1c3))
|
||||
* hide features ([9028675](https://github.com/remvze/moodist/commit/902867505743dd1dcd3f1e2afef010a186586615))
|
||||
* increase dots ([405fccc](https://github.com/remvze/moodist/commit/405fcccd95d9ce720f0731e8040006bd1d9b8bd2))
|
||||
* increase opacity ([882d440](https://github.com/remvze/moodist/commit/882d44079cfba8c7536c3713f0abeaa075ecb069))
|
||||
* increase padding ([8e50013](https://github.com/remvze/moodist/commit/8e500136cec6ba5580146306f25a5956aa3a2a4b))
|
||||
* increase pattern opacity ([5b83710](https://github.com/remvze/moodist/commit/5b83710c75515352b88c7bd361694d3067cb08fb))
|
||||
* lower opacity ([d4cc24e](https://github.com/remvze/moodist/commit/d4cc24e468230df51e5676abbd828b2f2edd97f3))
|
||||
* remove hero pattern ([8f36c86](https://github.com/remvze/moodist/commit/8f36c863d7f7489979691475947dbc8f29f26b39))
|
||||
* revert changes ([341a896](https://github.com/remvze/moodist/commit/341a896924a6ace70114ad2ae3349f8934a329ba))
|
||||
* revert pattern ([5916e86](https://github.com/remvze/moodist/commit/5916e86d3c6de9912b2c9bd490fa04cd9a0958dd))
|
||||
* show about and features ([37505a6](https://github.com/remvze/moodist/commit/37505a6b3f86919ac04b69519e56ddbaf5234843))
|
||||
|
||||
|
||||
### ✨ Features
|
||||
|
||||
* add about section ([d725d59](https://github.com/remvze/moodist/commit/d725d597034ead0bb63c5f0667b64ce459477662))
|
||||
* add active indicators ([240fd9c](https://github.com/remvze/moodist/commit/240fd9c6e05c7385c0de92b8b57776988b902fae))
|
||||
* add alarm for pomodoro timer ([0eb47ba](https://github.com/remvze/moodist/commit/0eb47ba2e1accaee7dd7d6114ca9a69cbc0656c4))
|
||||
* add basic pomodoro structure ([9f7de33](https://github.com/remvze/moodist/commit/9f7de336e5add254b40c5694fc4c619ee00602ba))
|
||||
* add controls to pomodoro ([7ed016d](https://github.com/remvze/moodist/commit/7ed016d8558a73d8d2bf05823cf80633882c1d69))
|
||||
* add copy for productivity toolbox ([3205145](https://github.com/remvze/moodist/commit/3205145d5425c7a7a8660b46aa9de0b273a04ff0))
|
||||
* add counter to notepad ([2424523](https://github.com/remvze/moodist/commit/24245235b14f9d59f86d2e988657a45a8a6d1eb7))
|
||||
* add CTA button ([0e12a52](https://github.com/remvze/moodist/commit/0e12a5203ef836bd262b3d4cc02aaeb9048f461e))
|
||||
* add custom presets ([2484e01](https://github.com/remvze/moodist/commit/2484e01273cf5e7ef5b44395cab26095891118fd))
|
||||
* add dividers to menu items ([408734d](https://github.com/remvze/moodist/commit/408734d49fd89fbd47d29527c03927e49c834f30))
|
||||
* add fade in/out ([663cb92](https://github.com/remvze/moodist/commit/663cb921350c083f1991665d147a3820bcdd9321))
|
||||
* add features section ([e4e332a](https://github.com/remvze/moodist/commit/e4e332ad75aad1a58fd97acb71660b8dec9dfa09))
|
||||
* add open-source section ([f7302de](https://github.com/remvze/moodist/commit/f7302dec5b7e182ad465bc30b63457a6e629a5b3))
|
||||
* add scroll for lower heights ([758f2f4](https://github.com/remvze/moodist/commit/758f2f48dc6a4e520b7a1e937f96eed28c8e8c20))
|
||||
* add simple notepad ([e923559](https://github.com/remvze/moodist/commit/e923559709796698257772cced4e20de584c6c80))
|
||||
* add source code item ([d055e66](https://github.com/remvze/moodist/commit/d055e66dd9dd5789c88d1a002686457ea89db073))
|
||||
* add special button ([a514a36](https://github.com/remvze/moodist/commit/a514a364ec5b6e2e34e7901ad51219d7be2aee86))
|
||||
* add titles ([5f40435](https://github.com/remvze/moodist/commit/5f40435c0ccfec0cb87d9ac0c14723fb8265fa8d))
|
||||
* add toolbar to notepad ([7463334](https://github.com/remvze/moodist/commit/7463334053ecd35a53cae535674169f5b50c81c2))
|
||||
* change alignments ([1a01a00](https://github.com/remvze/moodist/commit/1a01a0086648c7564ecab30b79df0d67e93eb392))
|
||||
* change the copy for features ([38da02a](https://github.com/remvze/moodist/commit/38da02a0d3b08e8f8802d6cf76a04ae656e10b76))
|
||||
* change tooltip content ([941e1f0](https://github.com/remvze/moodist/commit/941e1f024189143340d663a0c117c08a0b315599))
|
||||
* implement time setting ([f3cb2a1](https://github.com/remvze/moodist/commit/f3cb2a1b63e40f4f742ee2591b9353aa373f9783))
|
||||
* persist pomodoro setting ([665e217](https://github.com/remvze/moodist/commit/665e2173f4083128687a6302a6f2fd82674f07c1))
|
||||
* persist presets ([38a9a23](https://github.com/remvze/moodist/commit/38a9a23790248d5af522fc0d3cf6e99970e59637))
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* add correct count to description ([81e6666](https://github.com/remvze/moodist/commit/81e66667765879da624544c5d915c1562f2ab34c))
|
||||
* add key to categories ([38c11f1](https://github.com/remvze/moodist/commit/38c11f124e2235bc32de1e469b00ccaa22467a7e))
|
||||
* add min-width to inputs ([dfd6c1f](https://github.com/remvze/moodist/commit/dfd6c1fc4a41845e686fc6ee96f71b696213fe69))
|
||||
* change completion condition ([1ac5286](https://github.com/remvze/moodist/commit/1ac52861d1de651f8245d1e343307c6cf7a13dde))
|
||||
* change default times ([158cffc](https://github.com/remvze/moodist/commit/158cffca8c4b138f33e2df03e046706d2b122478))
|
||||
* change initial value ([a7e5368](https://github.com/remvze/moodist/commit/a7e53685918187c47d4fc2935418786b772c189e))
|
||||
* change z-index values ([79afb8d](https://github.com/remvze/moodist/commit/79afb8d92f9cb8e551bf101267018af1ab58815d))
|
||||
* comment out toolbox section ([a8a8c36](https://github.com/remvze/moodist/commit/a8a8c3643478d3da531e1da6c3640eb327acad3b))
|
||||
* make sound count dynamic ([f66a6ff](https://github.com/remvze/moodist/commit/f66a6ffde770992353a5b21fe65c20fe50ab4328))
|
||||
* remove extra headings ([7390a9b](https://github.com/remvze/moodist/commit/7390a9b3de0c52163d63b42ad48a882087886b65))
|
||||
* remove fading ([653d309](https://github.com/remvze/moodist/commit/653d309e64b770c2b270d2435bcd02345b686fec))
|
||||
* remove time from tabs array ([110356b](https://github.com/remvze/moodist/commit/110356b2da82e0f1e971ee9dc486664027d886ff))
|
||||
* remove word counter dependency ([ae3ea8c](https://github.com/remvze/moodist/commit/ae3ea8c74f9a94ae56a0eb4b165bc36d990dea7b))
|
||||
|
||||
### [1.3.1](https://github.com/remvze/moodist/compare/v1.3.0...v1.3.1) (2024-02-01)
|
||||
|
||||
|
||||
### ✨ Features
|
||||
|
||||
* add donate item ([f12ca48](https://github.com/remvze/moodist/commit/f12ca4806c9279f69f298bef770f8cac69a0860a))
|
||||
* add donate section ([d449c29](https://github.com/remvze/moodist/commit/d449c29321024a43517e92cc59223b4b22fe2e82))
|
||||
* add donation header ([17b4f25](https://github.com/remvze/moodist/commit/17b4f25ff10e09a917203e67cf963cac8358de1a))
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* coffee typo ([8e02910](https://github.com/remvze/moodist/commit/8e0291004a90e55b67a921b9ffb483b409109ae4))
|
||||
* complete donation links ([e6f768a](https://github.com/remvze/moodist/commit/e6f768a5e6dc983ae04b70f6c434fd4c13aeb506))
|
||||
|
||||
|
||||
### 🚚 Chores
|
||||
|
||||
* add donation link to README file ([1f806c4](https://github.com/remvze/moodist/commit/1f806c4e561d79a00850130eda09376299d85ed2))
|
||||
|
||||
## [1.3.0](https://github.com/remvze/moodist/compare/v1.2.0...v1.3.0) (2024-02-01)
|
||||
|
||||
|
||||
### ♻️ Code Refactoring
|
||||
|
||||
* remove media session ([1f63534](https://github.com/remvze/moodist/commit/1f635348e3e5cf73ee76e1c5fac7b5f5b7f7ea6a))
|
||||
* remove unmute and media session ([b77c817](https://github.com/remvze/moodist/commit/b77c817db25e1a738b6770b1ae86d792e0d42240))
|
||||
|
||||
|
||||
### ✨ Features
|
||||
|
||||
* add fading to intro and outro ([5467bbb](https://github.com/remvze/moodist/commit/5467bbbc2437a5504e157122a995ad7a565ff0b8))
|
||||
* add loader for favorites ([f682a91](https://github.com/remvze/moodist/commit/f682a910da97eb53cfb90ce955e953f05088e686))
|
||||
* add media session ([5e0a842](https://github.com/remvze/moodist/commit/5e0a84259ff5586700c4e10087485d905be7ccee))
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* add audio element ([889962b](https://github.com/remvze/moodist/commit/889962babe6e940ff283a41b145620d2a0477c70))
|
||||
* add media session ([81f33d9](https://github.com/remvze/moodist/commit/81f33d9d375f63b4dd0bf58ad28a72354d85706e))
|
||||
* add media session ([216b913](https://github.com/remvze/moodist/commit/216b913ccd0a7dfe0d03575f842aac9711ef0216))
|
||||
* add unmute for iOS ([e422b52](https://github.com/remvze/moodist/commit/e422b52436c7dfc0b6cf866afa2b74dc219dcf2f))
|
||||
* connect audio context to audio element ([463667c](https://github.com/remvze/moodist/commit/463667c868371540c46c9007e686961f9a4be7e5))
|
||||
* increase decimal ([a33ae45](https://github.com/remvze/moodist/commit/a33ae450cf2c883228c76d04df8df75839c12753))
|
||||
* remove fading ([d96461d](https://github.com/remvze/moodist/commit/d96461d1ea83c72bfe651d84cf34fabc029c200e))
|
||||
* resume audio ([8e4d053](https://github.com/remvze/moodist/commit/8e4d0531e0e9aaf4e52b3b3a8666b74ff0c0222e))
|
||||
* undo changes ([32da26c](https://github.com/remvze/moodist/commit/32da26ccfc0c5bdbe031e26ea48363ea0d8a7b23))
|
||||
|
||||
|
||||
### 🚚 Chores
|
||||
|
||||
* add binaural beats ([f1d212a](https://github.com/remvze/moodist/commit/f1d212abc8b69a614bbdc4a23876e2eab7cbb574))
|
||||
* add more sounds ([38f6f7d](https://github.com/remvze/moodist/commit/38f6f7dbe6898ed78e51eb3f0c7936f003ddca08))
|
||||
* add more sounds ([937bf29](https://github.com/remvze/moodist/commit/937bf29d09cbce20ea0b6b0c87879f3a7dd1d497))
|
||||
* add more sounds ([e2172fd](https://github.com/remvze/moodist/commit/e2172fd2bbd0e12a705c9efc98c72ad99d86d006))
|
||||
* add more sounds ([1f12afa](https://github.com/remvze/moodist/commit/1f12afa3943154d70145ef6adc6aeee79f7a7af3))
|
||||
* add more sounds ([cd05704](https://github.com/remvze/moodist/commit/cd05704a73ffb33aa0ccf5d789328a4cefc320f1))
|
||||
* add more sounds ([01b4bdb](https://github.com/remvze/moodist/commit/01b4bdbb572285984bcdc9bb94c1a1b6dd2630c5))
|
||||
|
||||
## [1.2.0](https://github.com/remvze/moodist/compare/v1.1.0...v1.2.0) (2024-01-04)
|
||||
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<img src="/assets/banner.svg" alt="Moodist Logo Banner" />
|
||||
<h2>Moodist 🌲</h2>
|
||||
<p>Ambient sounds for focus and calm.</p>
|
||||
<a href="https://moodist.app">Visit <strong>Moodist</strong> →</a>
|
||||
<a href="https://moodist.app">Visit <strong>Moodist</strong></a> | <a href="https://buymeacoffee.com/remvze">Buy Me a Coffee</a>
|
||||
</div>
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "moodist",
|
||||
"version": "1.2.0",
|
||||
"version": "1.4.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "moodist",
|
||||
"version": "1.2.0",
|
||||
"version": "1.4.2",
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^3.0.3",
|
||||
"@floating-ui/react": "0.26.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "moodist",
|
||||
"type": "module",
|
||||
"version": "1.2.0",
|
||||
"version": "1.4.2",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
|
||||
BIN
public/sounds/alarm.mp3
Normal file
BIN
public/sounds/alarm.mp3
Normal file
Binary file not shown.
BIN
public/sounds/animals/crows.mp3
Normal file
BIN
public/sounds/animals/crows.mp3
Normal file
Binary file not shown.
BIN
public/sounds/animals/whale.mp3
Normal file
BIN
public/sounds/animals/whale.mp3
Normal file
Binary file not shown.
BIN
public/sounds/binaural/binaural-alpha.wav
Normal file
BIN
public/sounds/binaural/binaural-alpha.wav
Normal file
Binary file not shown.
BIN
public/sounds/binaural/binaural-beta.wav
Normal file
BIN
public/sounds/binaural/binaural-beta.wav
Normal file
Binary file not shown.
BIN
public/sounds/binaural/binaural-delta.wav
Normal file
BIN
public/sounds/binaural/binaural-delta.wav
Normal file
Binary file not shown.
BIN
public/sounds/binaural/binaural-gamma.wav
Normal file
BIN
public/sounds/binaural/binaural-gamma.wav
Normal file
Binary file not shown.
BIN
public/sounds/binaural/binaural-theta.wav
Normal file
BIN
public/sounds/binaural/binaural-theta.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/sounds/places/carousel.mp3
Normal file
BIN
public/sounds/places/carousel.mp3
Normal file
Binary file not shown.
BIN
public/sounds/places/crowded-bar.mp3
Normal file
BIN
public/sounds/places/crowded-bar.mp3
Normal file
Binary file not shown.
BIN
public/sounds/places/laboratory.mp3
Normal file
BIN
public/sounds/places/laboratory.mp3
Normal file
Binary file not shown.
BIN
public/sounds/places/night-village.mp3
Normal file
BIN
public/sounds/places/night-village.mp3
Normal file
Binary file not shown.
BIN
public/sounds/places/office.mp3
Normal file
BIN
public/sounds/places/office.mp3
Normal file
Binary file not shown.
BIN
public/sounds/places/subway-station.mp3
Normal file
BIN
public/sounds/places/subway-station.mp3
Normal file
Binary file not shown.
BIN
public/sounds/places/supermarket.mp3
Normal file
BIN
public/sounds/places/supermarket.mp3
Normal file
Binary file not shown.
BIN
public/sounds/things/boiling-water.mp3
Normal file
BIN
public/sounds/things/boiling-water.mp3
Normal file
Binary file not shown.
BIN
public/sounds/things/bubbles.mp3
Normal file
BIN
public/sounds/things/bubbles.mp3
Normal file
Binary file not shown.
BIN
public/sounds/things/dryer.mp3
Normal file
BIN
public/sounds/things/dryer.mp3
Normal file
Binary file not shown.
BIN
public/sounds/things/morse-code.mp3
Normal file
BIN
public/sounds/things/morse-code.mp3
Normal file
Binary file not shown.
BIN
public/sounds/things/slide-projector.mp3
Normal file
BIN
public/sounds/things/slide-projector.mp3
Normal file
Binary file not shown.
BIN
public/sounds/things/tuning-radio.mp3
Normal file
BIN
public/sounds/things/tuning-radio.mp3
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/sounds/urban/fireworks.mp3
Normal file
BIN
public/sounds/urban/fireworks.mp3
Normal file
Binary file not shown.
90
src/components/about/about.module.css
Normal file
90
src/components/about/about.module.css
Normal file
@@ -0,0 +1,90 @@
|
||||
.about {
|
||||
padding-top: 10px;
|
||||
|
||||
& .effect {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 80px;
|
||||
background: linear-gradient(var(--color-neutral-50), transparent);
|
||||
}
|
||||
|
||||
& .paragraph {
|
||||
padding: 30px 0;
|
||||
background: linear-gradient(
|
||||
transparent,
|
||||
var(--color-neutral-50) 10%,
|
||||
var(--color-neutral-50) 90%,
|
||||
transparent
|
||||
);
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
& .counter {
|
||||
width: max-content;
|
||||
padding: 6px 16px;
|
||||
margin-bottom: 16px;
|
||||
font-size: var(--font-xsm);
|
||||
color: var(--color-foreground-subtle);
|
||||
background: linear-gradient(var(--color-neutral-100), transparent);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 20px 20px 20px 8px;
|
||||
|
||||
& span {
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
& .title {
|
||||
margin-bottom: 8px;
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--font-md);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
& .body {
|
||||
line-height: 1.6;
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 16px;
|
||||
margin-top: 20px;
|
||||
font-size: var(--font-xsm);
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 50px;
|
||||
outline: none;
|
||||
transition: 0.2s;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 50%;
|
||||
width: 70%;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--color-neutral-300),
|
||||
transparent
|
||||
);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-neutral-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/components/about/about.tsx
Normal file
60
src/components/about/about.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Container } from '@/components/container';
|
||||
import { count as soundCount } from '@/lib/sounds';
|
||||
|
||||
import styles from './about.module.css';
|
||||
|
||||
export function About() {
|
||||
const count = soundCount();
|
||||
|
||||
const paragraphs = [
|
||||
{
|
||||
body: 'Craving a calming escape from the daily grind? Do you need the perfect soundscape to boost your focus or lull you into peaceful sleep? Look no further than Moodist, your free and open-source ambient sound generator! Ditch the subscriptions and registrations – with Moodist, you unlock a world of soothing and immersive audio experiences, entirely for free.',
|
||||
title: 'Free Ambient Sounds',
|
||||
},
|
||||
{
|
||||
body: `Dive into an expansive library of ${count} carefully curated sounds. Nature lovers will find solace in the gentle murmur of streams, the rhythmic crash of waves, or the crackling warmth of a campfire. Cityscapes come alive with the soft hum of cafes, the rhythmic clatter of trains, or the calming white noise of traffic. And for those seeking deeper focus or relaxation, Moodist offers binaural beats and color noise designed to enhance your state of mind.`,
|
||||
title: 'Carefully Curated Sounds',
|
||||
},
|
||||
{
|
||||
body: 'The beauty of Moodist lies in its simplicity and customization. No complex menus or confusing options – just choose your desired sounds, adjust the volume balance, and hit play. Want to blend the gentle chirping of birds with the soothing sound of rain? No problem! Layer as many sounds as you like to create your personalized soundscape oasis.',
|
||||
title: 'Create Your Soundscape',
|
||||
},
|
||||
// {
|
||||
// body: 'Moodist goes beyond just ambient sounds by offering a suite of productivity tools to help you stay organized and focused. Utilize the built-in pomodoro timer to structure your workday in focused intervals, jot down thoughts and ideas in the simple notepad, and keep track of your tasks with the handy to-do list. These tools seamlessly integrate with the ambient soundscapes, allowing you to create a personalized environment that fosters both focus and relaxation.',
|
||||
// title: 'A Productivity Toolbox',
|
||||
// },
|
||||
{
|
||||
body: "Whether you're looking to unwind after a long day, enhance your focus during work, or lull yourself into a peaceful sleep, Moodist has the perfect soundscape waiting for you. The best part? It's completely free and open-source, so you can enjoy its benefits without any strings attached. Start using Moodist today and discover your new haven of tranquility and focus!",
|
||||
title: 'Sounds for Every Moment',
|
||||
},
|
||||
];
|
||||
|
||||
const handleClick = () => {
|
||||
const app = document.getElementById('app');
|
||||
|
||||
app?.scrollIntoView();
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={styles.about}>
|
||||
<div className={styles.effect} />
|
||||
|
||||
<Container tight>
|
||||
{paragraphs.map((paragraph, index) => (
|
||||
<div className={styles.paragraph} key={index}>
|
||||
<div className={styles.counter}>
|
||||
<span>0{index + 1}</span> / 0{paragraphs.length}
|
||||
</div>
|
||||
|
||||
<h2 className={styles.title}>{paragraph.title}</h2>
|
||||
<p className={styles.body}>{paragraph.body}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button className={styles.button} onClick={handleClick}>
|
||||
Use Moodist
|
||||
</button>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
1
src/components/about/index.ts
Normal file
1
src/components/about/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { About } from './about';
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useEffect } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { BiSolidHeart } from 'react-icons/bi/index';
|
||||
import { Howler } from 'howler';
|
||||
|
||||
import { useSoundStore } from '@/store';
|
||||
|
||||
@@ -8,9 +9,8 @@ import { Container } from '@/components/container';
|
||||
import { StoreConsumer } from '@/components/store-consumer';
|
||||
import { Buttons } from '@/components/buttons';
|
||||
import { Categories } from '@/components/categories';
|
||||
import { ScrollToTop } from '@/components/scroll-to-top';
|
||||
import { SharedModal } from '@/components/modals/shared';
|
||||
import { Menu } from '@/components/menu/menu';
|
||||
import { Toolbar } from '@/components/toolbar';
|
||||
import { SnackbarProvider } from '@/contexts/snackbar';
|
||||
|
||||
import { sounds } from '@/data/sounds';
|
||||
@@ -36,6 +36,22 @@ export function App() {
|
||||
);
|
||||
}, [favorites, categories]);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = () => {
|
||||
const { ctx } = Howler;
|
||||
|
||||
if (ctx && !document.hidden) {
|
||||
setTimeout(() => {
|
||||
ctx.resume();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', onChange, false);
|
||||
|
||||
return () => document.removeEventListener('visibilitychange', onChange);
|
||||
}, []);
|
||||
|
||||
const allCategories = useMemo(() => {
|
||||
const favorites = [];
|
||||
|
||||
@@ -60,8 +76,7 @@ export function App() {
|
||||
<Categories categories={allCategories} />
|
||||
</Container>
|
||||
|
||||
<ScrollToTop />
|
||||
<Menu />
|
||||
<Toolbar />
|
||||
<SharedModal />
|
||||
</StoreConsumer>
|
||||
</SnackbarProvider>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
|
||||
import { Category } from '@/components/category';
|
||||
import { Donate } from './donate';
|
||||
|
||||
import type { Categories } from '@/data/types';
|
||||
|
||||
@@ -11,12 +12,12 @@ interface CategoriesProps {
|
||||
export function Categories({ categories }: CategoriesProps) {
|
||||
return (
|
||||
<AnimatePresence initial={false}>
|
||||
{categories.map(category => (
|
||||
<Category
|
||||
functional={category.id !== 'favorites'}
|
||||
{...category}
|
||||
key={category.id}
|
||||
/>
|
||||
{categories.map((category, index) => (
|
||||
<div key={category.id}>
|
||||
<Category functional={category.id !== 'favorites'} {...category} />
|
||||
|
||||
{index === 3 && <Donate />}
|
||||
</div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
||||
45
src/components/categories/donate/donate.module.css
Normal file
45
src/components/categories/donate/donate.module.css
Normal file
@@ -0,0 +1,45 @@
|
||||
.donate {
|
||||
margin-bottom: 20px;
|
||||
|
||||
& .iconContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
& .tail {
|
||||
width: 1px;
|
||||
height: 75px;
|
||||
background: linear-gradient(transparent, var(--color-neutral-300));
|
||||
}
|
||||
|
||||
& .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
font-size: var(--font-md);
|
||||
background-color: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
& .title {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--font-lg);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .desc {
|
||||
margin-top: 8px;
|
||||
color: var(--color-foreground-subtle);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 16px auto 0;
|
||||
}
|
||||
}
|
||||
27
src/components/categories/donate/donate.tsx
Normal file
27
src/components/categories/donate/donate.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { FaCoffee } from 'react-icons/fa/index';
|
||||
|
||||
import { SpecialButton } from '@/components/special-button';
|
||||
|
||||
import styles from './donate.module.css';
|
||||
|
||||
export function Donate() {
|
||||
return (
|
||||
<div className={styles.donate}>
|
||||
<div className={styles.iconContainer}>
|
||||
<div className={styles.tail} />
|
||||
<div className={styles.icon}>
|
||||
<FaCoffee />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.title}>Support Me</div>
|
||||
<p className={styles.desc}>Help me keep Moodist ad-free.</p>
|
||||
<SpecialButton
|
||||
className={styles.button}
|
||||
href="https://buymeacoffee.com/remvze"
|
||||
>
|
||||
Donate Today
|
||||
</SpecialButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
src/components/categories/donate/index.ts
Normal file
1
src/components/categories/donate/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Donate } from './donate';
|
||||
@@ -22,7 +22,7 @@ export function Category({
|
||||
<div className={styles.icon}>{icon}</div>
|
||||
</div>
|
||||
|
||||
<h2 className={styles.title}>{title}</h2>
|
||||
<div className={styles.title}>{title}</div>
|
||||
|
||||
<Sounds functional={functional} id={id} sounds={sounds} />
|
||||
</div>
|
||||
|
||||
@@ -2,4 +2,12 @@
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
|
||||
&.tight {
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
&.wide {
|
||||
max-width: 760px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
import { cn } from '@/helpers/styles';
|
||||
|
||||
import styles from './container.module.css';
|
||||
|
||||
interface ContainerProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
tight?: boolean;
|
||||
wide?: boolean;
|
||||
}
|
||||
|
||||
export function Container({ children }: ContainerProps) {
|
||||
return <div className={styles.container}>{children}</div>;
|
||||
export function Container({
|
||||
children,
|
||||
className,
|
||||
tight,
|
||||
wide,
|
||||
}: ContainerProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
styles.container,
|
||||
className,
|
||||
tight && styles.tight,
|
||||
wide && styles.wide,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
36
src/components/donate/donate.module.css
Normal file
36
src/components/donate/donate.module.css
Normal file
@@ -0,0 +1,36 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
font-size: var(--font-xsm);
|
||||
color: var(--color-foreground-subtle);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 80%;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--color-neutral-200),
|
||||
transparent
|
||||
);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
& .text {
|
||||
text-align: center;
|
||||
|
||||
& a {
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/components/donate/donate.tsx
Normal file
22
src/components/donate/donate.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Container } from '@/components/container';
|
||||
|
||||
import styles from './donate.module.css';
|
||||
|
||||
export function Donate() {
|
||||
return (
|
||||
<Container>
|
||||
<section className={styles.wrapper}>
|
||||
<p className={styles.text}>
|
||||
Enjoy Moodist?{' '}
|
||||
<a
|
||||
href="https://buymeacoffee.com/remvze"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Support with a donation!
|
||||
</a>
|
||||
</p>
|
||||
</section>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
1
src/components/donate/index.ts
Normal file
1
src/components/donate/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Donate } from './donate';
|
||||
99
src/components/features/features.module.css
Normal file
99
src/components/features/features.module.css
Normal file
@@ -0,0 +1,99 @@
|
||||
.featuresSection {
|
||||
margin-top: 40px;
|
||||
|
||||
& .iconContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
& .tail {
|
||||
width: 1px;
|
||||
height: 75px;
|
||||
background: linear-gradient(transparent, var(--color-neutral-300));
|
||||
}
|
||||
|
||||
& .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
font-size: var(--font-md);
|
||||
background-color: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
& .title {
|
||||
margin-bottom: 8px;
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--font-lg);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
row-gap: 32px;
|
||||
column-gap: 20px;
|
||||
margin-top: 24px;
|
||||
|
||||
& .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 12px;
|
||||
font-size: var(--font-md);
|
||||
color: var(--color-foreground-subtle);
|
||||
background: linear-gradient(var(--color-neutral-100), transparent);
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
& .label {
|
||||
margin-bottom: 8px;
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .body {
|
||||
width: 100%;
|
||||
max-width: 275px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.6;
|
||||
color: var(--color-foreground-subtle);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .link {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
& .soon {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
padding: 6px 12px;
|
||||
margin: 0 auto;
|
||||
margin-top: 8px;
|
||||
font-size: var(--font-2xsm);
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
background: linear-gradient(var(--color-neutral-100), transparent);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
src/components/features/features.tsx
Normal file
107
src/components/features/features.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import { BiMoney, BiUserCircle, BiLogoGithub } from 'react-icons/bi/index';
|
||||
import { BsSoundwave, BsStars } from 'react-icons/bs/index';
|
||||
import { RxMixerHorizontal } from 'react-icons/rx/index';
|
||||
|
||||
import { Balancer } from 'react-wrap-balancer';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
import { count as soundCount } from '@/lib/sounds';
|
||||
|
||||
import styles from './features.module.css';
|
||||
|
||||
export function Features() {
|
||||
const count = soundCount();
|
||||
|
||||
const features = [
|
||||
{
|
||||
Icon: BiMoney,
|
||||
body: 'Immerse yourself in sound without spending a dime.',
|
||||
id: 'free-access',
|
||||
label: 'Free Access',
|
||||
},
|
||||
{
|
||||
Icon: BiUserCircle,
|
||||
body: 'Dive right in, no sign-up hoops to jump through.',
|
||||
id: 'no-registration',
|
||||
label: 'No Registration',
|
||||
},
|
||||
{
|
||||
Icon: BsSoundwave,
|
||||
body: `Explore ${count} unique soundscapes, from rainforests to cityscapes.`,
|
||||
id: 'diverse-sounds',
|
||||
label: 'Diverse Sounds',
|
||||
},
|
||||
{
|
||||
Icon: RxMixerHorizontal,
|
||||
body: 'Craft your perfect soundscape by blending and adjusting sounds.',
|
||||
id: 'customizable-mixes',
|
||||
label: 'Customizable Mixes',
|
||||
},
|
||||
{
|
||||
Icon: BiLogoGithub,
|
||||
body: 'Contribute and collaborate, making the best even better.',
|
||||
id: 'open-source',
|
||||
label: 'Open-Source',
|
||||
link: {
|
||||
label: 'Source Code',
|
||||
url: 'https://github.com/remvze/moodist',
|
||||
},
|
||||
},
|
||||
{
|
||||
Icon: BsStars,
|
||||
body: 'Uninterrupted immersion, focus on the sounds, not the tech.',
|
||||
id: 'seamless-experience',
|
||||
label: 'Seamless Experience',
|
||||
},
|
||||
{
|
||||
Icon: BsStars,
|
||||
body: 'Spread the calm, easily share your customized sound blends.',
|
||||
id: 'share-selections',
|
||||
label: 'Share Selections',
|
||||
},
|
||||
{
|
||||
Icon: BsStars,
|
||||
body: 'Lock in your favorite mixes for instant return to your sonic haven.',
|
||||
id: 'save-presets',
|
||||
label: 'Save Presets',
|
||||
soon: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={styles.featuresSection}>
|
||||
<Container>
|
||||
<div className={styles.iconContainer}>
|
||||
<div className={styles.tail} />
|
||||
<div className={styles.icon}>
|
||||
<BsStars />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className={styles.title}>Features</h2>
|
||||
|
||||
<div className={styles.features}>
|
||||
{features.map(feature => (
|
||||
<div className={styles.reason} key={feature.id}>
|
||||
<div className={styles.icon}>
|
||||
<feature.Icon />
|
||||
</div>
|
||||
<h3 className={styles.label}>{feature.label}</h3>
|
||||
<p className={styles.body}>
|
||||
<Balancer>{feature.body}</Balancer>
|
||||
</p>
|
||||
|
||||
{feature.link && (
|
||||
<a className={styles.link} href={feature.link.url}>
|
||||
{feature.link.label}
|
||||
</a>
|
||||
)}
|
||||
|
||||
{feature.soon && <div className={styles.soon}>Coming Soon</div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
1
src/components/features/index.ts
Normal file
1
src/components/features/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Features } from './features';
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
import { Container } from '@/components/container';
|
||||
---
|
||||
|
||||
<footer class="footer">
|
||||
<Container>
|
||||
<p>
|
||||
Created by{' '}
|
||||
<a href="https://twitter.com/remvze">
|
||||
Maze <span>✦</span>
|
||||
</a>
|
||||
</p>
|
||||
</Container>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
|
||||
& p {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--color-foreground-subtle);
|
||||
text-align: center;
|
||||
|
||||
& a {
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
text-decoration: none;
|
||||
|
||||
& span {
|
||||
color: #c0eb75;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
17
src/components/footer/footer.module.css
Normal file
17
src/components/footer/footer.module.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
|
||||
& p {
|
||||
font-size: var(--font-sm);
|
||||
color: var(--color-foreground-subtle);
|
||||
text-align: center;
|
||||
|
||||
& a {
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/components/footer/footer.tsx
Normal file
15
src/components/footer/footer.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Container } from '@/components/container';
|
||||
|
||||
import styles from './footer.module.css';
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<Container>
|
||||
<p>
|
||||
Created by <a href="https://twitter.com/remvze">Maze ✦</a>
|
||||
</p>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
1
src/components/footer/index.ts
Normal file
1
src/components/footer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Footer } from './footer';
|
||||
@@ -1,131 +0,0 @@
|
||||
---
|
||||
import { Balancer } from 'react-wrap-balancer';
|
||||
import { BsSoundwave } from 'react-icons/bs/index';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
import { count as soundCount } from '@/lib/sounds';
|
||||
|
||||
const count = soundCount();
|
||||
---
|
||||
|
||||
<div class="hero">
|
||||
<Container>
|
||||
<img
|
||||
alt="Faded Moodist Logo"
|
||||
class="logo"
|
||||
height={45}
|
||||
src="/logo.svg"
|
||||
width={45}
|
||||
/>
|
||||
|
||||
<div class="title">
|
||||
<div class="left"></div>
|
||||
<h1>Moodist</h1>
|
||||
<div class="right"></div>
|
||||
</div>
|
||||
|
||||
<p class="desc">
|
||||
<Balancer>Ambient sounds for focus and calm.</Balancer>
|
||||
</p>
|
||||
|
||||
<p class="sounds">
|
||||
<span class="icon"><BsSoundwave /></span>
|
||||
<span>{count} Sounds</span>
|
||||
</p>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
padding: 140px 0 60px;
|
||||
text-align: center;
|
||||
|
||||
& .logo {
|
||||
display: block;
|
||||
width: 45px;
|
||||
margin: 0 auto 12px;
|
||||
}
|
||||
|
||||
& .title {
|
||||
display: flex;
|
||||
column-gap: 15px;
|
||||
align-items: center;
|
||||
|
||||
& div {
|
||||
flex-grow: 1;
|
||||
height: 1px;
|
||||
|
||||
&.left {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--color-neutral-300)
|
||||
);
|
||||
}
|
||||
|
||||
&.right {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-neutral-300),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
& h1 {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--font-2xlg);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
& .desc {
|
||||
margin-top: 5px;
|
||||
line-height: 1.6;
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
|
||||
& .sounds {
|
||||
position: relative;
|
||||
display: flex;
|
||||
column-gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: max-content;
|
||||
height: 28px;
|
||||
padding-right: 12px;
|
||||
margin: 20px auto 0;
|
||||
font-size: var(--font-xsm);
|
||||
color: var(--color-foreground-subtle);
|
||||
background-color: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 100px;
|
||||
|
||||
& .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 0 8px 0 12px;
|
||||
color: var(--color-foreground);
|
||||
border-right: 1px solid var(--color-neutral-200);
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 50%;
|
||||
width: 70%;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--color-neutral-400),
|
||||
transparent
|
||||
);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
120
src/components/hero/hero.module.css
Normal file
120
src/components/hero/hero.module.css
Normal file
@@ -0,0 +1,120 @@
|
||||
.hero {
|
||||
text-align: center;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
padding: 100px 0 80px;
|
||||
|
||||
/* padding: 120px 0 60px; */
|
||||
|
||||
& .pattern {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: radial-gradient(
|
||||
var(--color-neutral-300) 5%,
|
||||
transparent 5%
|
||||
);
|
||||
background-position: top center;
|
||||
background-size: 31px 31px;
|
||||
opacity: 0.9;
|
||||
mask-image: linear-gradient(#fff, transparent, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
& .logo {
|
||||
display: block;
|
||||
width: 45px;
|
||||
margin: 0 auto 12px;
|
||||
}
|
||||
|
||||
& .title {
|
||||
display: flex;
|
||||
column-gap: 15px;
|
||||
align-items: center;
|
||||
|
||||
& div {
|
||||
flex-grow: 1;
|
||||
height: 1px;
|
||||
|
||||
&.left {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
transparent,
|
||||
var(--color-neutral-200),
|
||||
var(--color-neutral-300)
|
||||
);
|
||||
}
|
||||
|
||||
&.right {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-neutral-300),
|
||||
var(--color-neutral-200),
|
||||
transparent,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
& h2 {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--font-2xlg);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
& .desc {
|
||||
margin-top: 5px;
|
||||
line-height: 1.6;
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
|
||||
& .sounds {
|
||||
position: relative;
|
||||
display: flex;
|
||||
column-gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: max-content;
|
||||
height: 28px;
|
||||
padding-right: 12px;
|
||||
margin: 20px auto 0;
|
||||
font-size: var(--font-xsm);
|
||||
color: var(--color-foreground-subtle);
|
||||
background: linear-gradient(var(--color-neutral-100), transparent);
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 100px;
|
||||
|
||||
& .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 0 10px;
|
||||
color: var(--color-foreground);
|
||||
border-right: 1px solid var(--color-neutral-200);
|
||||
border-radius: 0 100px 100px 0;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 50%;
|
||||
width: 70%;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--color-neutral-400),
|
||||
transparent
|
||||
);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/components/hero/hero.tsx
Normal file
42
src/components/hero/hero.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useMemo } from 'react';
|
||||
import { BsSoundwave } from 'react-icons/bs/index';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
import { count as soundCount } from '@/lib/sounds';
|
||||
|
||||
import styles from './hero.module.css';
|
||||
|
||||
export function Hero() {
|
||||
const count = useMemo(soundCount, []);
|
||||
|
||||
return (
|
||||
<div className={styles.hero}>
|
||||
<Container className={styles.container}>
|
||||
{/* <div className={styles.pattern} /> */}
|
||||
|
||||
<img
|
||||
alt="Faded Moodist Logo"
|
||||
className={styles.logo}
|
||||
height={45}
|
||||
src="/logo.svg"
|
||||
width={45}
|
||||
/>
|
||||
|
||||
<div className={styles.title}>
|
||||
<div className={styles.left}></div>
|
||||
<h2>Moodist</h2>
|
||||
<div className={styles.right}></div>
|
||||
</div>
|
||||
|
||||
<h1 className={styles.desc}>Ambient sounds for focus and calm.</h1>
|
||||
|
||||
<p className={styles.sounds}>
|
||||
<span className={styles.icon}>
|
||||
<BsSoundwave />
|
||||
</span>
|
||||
<span>{count} Sounds</span>
|
||||
</p>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
src/components/hero/index.ts
Normal file
1
src/components/hero/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Hero } from './hero';
|
||||
6
src/components/menu/divider/divider.module.css
Normal file
6
src/components/menu/divider/divider.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
min-height: 1px;
|
||||
background-color: var(--color-neutral-200);
|
||||
}
|
||||
5
src/components/menu/divider/divider.tsx
Normal file
5
src/components/menu/divider/divider.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import styles from './divider.module.css';
|
||||
|
||||
export function Divider() {
|
||||
return <div className={styles.divider} />;
|
||||
}
|
||||
1
src/components/menu/divider/index.ts
Normal file
1
src/components/menu/divider/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Divider } from './divider';
|
||||
@@ -4,15 +4,17 @@
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding: 16px 12px;
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
padding: 0 12px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: var(--color-foreground-subtle);
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
transition: 0.2s;
|
||||
@@ -25,9 +27,17 @@
|
||||
&:not(:disabled):hover {
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-neutral-200);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
}
|
||||
|
||||
& .icon {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
& .active {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: var(--color-neutral-950);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
import styles from './item.module.css';
|
||||
|
||||
interface ItemProps {
|
||||
disabled: boolean;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
href?: string;
|
||||
icon: React.ReactElement;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function Item({ disabled = false, icon, label, onClick }: ItemProps) {
|
||||
export function Item({
|
||||
active,
|
||||
disabled = false,
|
||||
href,
|
||||
icon,
|
||||
label,
|
||||
onClick = () => {},
|
||||
}: ItemProps) {
|
||||
const Comp = href ? 'a' : 'button';
|
||||
|
||||
return (
|
||||
<button className={styles.item} disabled={disabled} onClick={onClick}>
|
||||
<Comp
|
||||
className={styles.item}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
{...(href ? { href, target: '_blank' } : {})}
|
||||
>
|
||||
<span className={styles.icon}>{icon}</span> {label}
|
||||
</button>
|
||||
{active && <div className={styles.active} />}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
|
||||
13
src/components/menu/items/donate.tsx
Normal file
13
src/components/menu/items/donate.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { SiBuymeacoffee } from 'react-icons/si/index';
|
||||
|
||||
import { Item } from '../item';
|
||||
|
||||
export function Donate() {
|
||||
return (
|
||||
<Item
|
||||
href="https://buymeacoffee.com/remvze"
|
||||
icon={<SiBuymeacoffee />}
|
||||
label="Buy Me a Coffee"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +1,7 @@
|
||||
export { Shuffle as ShuffleItem } from './shuffle';
|
||||
export { Share as ShareItem } from './share';
|
||||
export { Donate as DonateItem } from './donate';
|
||||
export { Notepad as NotepadItem } from './notepad';
|
||||
export { Source as SourceItem } from './source';
|
||||
export { Pomodoro as PomodoroItem } from './pomodoro';
|
||||
export { Presets as PresetsItem } from './presets';
|
||||
|
||||
22
src/components/menu/items/notepad.tsx
Normal file
22
src/components/menu/items/notepad.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { MdNotes } from 'react-icons/md/index';
|
||||
|
||||
import { Item } from '../item';
|
||||
|
||||
import { useNoteStore } from '@/store';
|
||||
|
||||
interface NotepadProps {
|
||||
open: () => void;
|
||||
}
|
||||
|
||||
export function Notepad({ open }: NotepadProps) {
|
||||
const note = useNoteStore(state => state.note);
|
||||
|
||||
return (
|
||||
<Item
|
||||
active={!!note.length}
|
||||
icon={<MdNotes />}
|
||||
label="Notepad"
|
||||
onClick={open}
|
||||
/>
|
||||
);
|
||||
}
|
||||
22
src/components/menu/items/pomodoro.tsx
Normal file
22
src/components/menu/items/pomodoro.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { MdOutlineAvTimer } from 'react-icons/md/index';
|
||||
|
||||
import { Item } from '../item';
|
||||
|
||||
import { usePomodoroStore } from '@/store';
|
||||
|
||||
interface PomodoroProps {
|
||||
open: () => void;
|
||||
}
|
||||
|
||||
export function Pomodoro({ open }: PomodoroProps) {
|
||||
const running = usePomodoroStore(state => state.running);
|
||||
|
||||
return (
|
||||
<Item
|
||||
active={running}
|
||||
icon={<MdOutlineAvTimer />}
|
||||
label="Pomodoro"
|
||||
onClick={open}
|
||||
/>
|
||||
);
|
||||
}
|
||||
11
src/components/menu/items/presets.tsx
Normal file
11
src/components/menu/items/presets.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { RiPlayListFill } from 'react-icons/ri/index';
|
||||
|
||||
import { Item } from '../item';
|
||||
|
||||
interface PresetsProps {
|
||||
open: () => void;
|
||||
}
|
||||
|
||||
export function Presets({ open }: PresetsProps) {
|
||||
return <Item icon={<RiPlayListFill />} label="Your Presets" onClick={open} />;
|
||||
}
|
||||
13
src/components/menu/items/source.tsx
Normal file
13
src/components/menu/items/source.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { LuGithub } from 'react-icons/lu/index';
|
||||
|
||||
import { Item } from '../item';
|
||||
|
||||
export function Source() {
|
||||
return (
|
||||
<Item
|
||||
href="https://github.com/remvze/moodist"
|
||||
icon={<LuGithub />}
|
||||
label="Source Code"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
.wrapper {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
z-index: 5;
|
||||
|
||||
& .menuButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -28,30 +23,11 @@
|
||||
flex-direction: column;
|
||||
row-gap: 4px;
|
||||
width: 240px;
|
||||
height: max-content;
|
||||
padding: 4px;
|
||||
overflow: auto;
|
||||
background-color: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 4px;
|
||||
|
||||
& .menuItem {
|
||||
position: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 12px 8px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground-subtle);
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
transition: 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-neutral-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
import { IoMenu, IoClose } from 'react-icons/io5/index';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import {
|
||||
useFloating,
|
||||
autoUpdate,
|
||||
offset,
|
||||
flip,
|
||||
shift,
|
||||
size,
|
||||
useClick,
|
||||
useDismiss,
|
||||
useRole,
|
||||
@@ -14,22 +14,44 @@ import {
|
||||
FloatingFocusManager,
|
||||
} from '@floating-ui/react';
|
||||
|
||||
import { ShuffleItem, ShareItem } from './items';
|
||||
import {
|
||||
ShuffleItem,
|
||||
ShareItem,
|
||||
DonateItem,
|
||||
NotepadItem,
|
||||
SourceItem,
|
||||
PomodoroItem,
|
||||
PresetsItem,
|
||||
} from './items';
|
||||
import { Divider } from './divider';
|
||||
import { ShareLinkModal } from '@/components/modals/share-link';
|
||||
|
||||
import { slideY, fade, mix } from '@/lib/motion';
|
||||
import { PresetsModal } from '@/components/modals/presets';
|
||||
import { Notepad, Pomodoro } from '@/components/toolbox';
|
||||
|
||||
import styles from './menu.module.css';
|
||||
|
||||
export function Menu() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [showPresets, setShowPresets] = useState(false);
|
||||
const [showShareLink, setShowShareLink] = useState(false);
|
||||
|
||||
const variants = mix(slideY(-20), fade());
|
||||
const [showNotepad, setShowNotepad] = useState(false);
|
||||
const [showPomodoro, setShowPomodoro] = useState(false);
|
||||
|
||||
const { context, floatingStyles, refs } = useFloating({
|
||||
middleware: [offset(12), flip(), shift()],
|
||||
middleware: [
|
||||
offset(12),
|
||||
flip(),
|
||||
shift(),
|
||||
size({
|
||||
apply({ availableHeight, elements }) {
|
||||
Object.assign(elements.floating.style, {
|
||||
maxHeight: `${availableHeight}px`,
|
||||
});
|
||||
},
|
||||
padding: 10,
|
||||
}),
|
||||
],
|
||||
onOpenChange: setIsOpen,
|
||||
open: isOpen,
|
||||
placement: 'top-end',
|
||||
@@ -59,34 +81,37 @@ export function Menu() {
|
||||
{isOpen ? <IoClose /> : <IoMenu />}
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<FloatingFocusManager context={context} modal={false}>
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
<motion.div
|
||||
animate="show"
|
||||
className={styles.menu}
|
||||
exit="hidden"
|
||||
initial="hidden"
|
||||
variants={variants}
|
||||
>
|
||||
<ShareItem open={() => setShowShareLink(true)} />
|
||||
<ShuffleItem />
|
||||
</motion.div>
|
||||
</div>
|
||||
</FloatingFocusManager>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{isOpen && (
|
||||
<FloatingFocusManager context={context} modal={false}>
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
{...getFloatingProps()}
|
||||
className={styles.menu}
|
||||
>
|
||||
<PresetsItem open={() => setShowPresets(true)} />
|
||||
<ShareItem open={() => setShowShareLink(true)} />
|
||||
<ShuffleItem />
|
||||
<Divider />
|
||||
<NotepadItem open={() => setShowNotepad(true)} />
|
||||
<PomodoroItem open={() => setShowPomodoro(true)} />
|
||||
<Divider />
|
||||
<DonateItem />
|
||||
<SourceItem />
|
||||
</div>
|
||||
</FloatingFocusManager>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ShareLinkModal
|
||||
show={showShareLink}
|
||||
onClose={() => setShowShareLink(false)}
|
||||
/>
|
||||
|
||||
<PresetsModal show={showPresets} onClose={() => setShowPresets(false)} />
|
||||
|
||||
<Notepad show={showNotepad} onClose={() => setShowNotepad(false)} />
|
||||
<Pomodoro show={showPomodoro} onClose={() => setShowPomodoro(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10;
|
||||
z-index: 20;
|
||||
background-color: rgb(9 9 11 / 40%);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 12;
|
||||
z-index: 20;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
padding: 50px 0;
|
||||
@@ -29,6 +29,13 @@
|
||||
background-color: var(--color-neutral-100);
|
||||
border-radius: 8px;
|
||||
|
||||
&.wide {
|
||||
width: 95%;
|
||||
max-width: 600px;
|
||||
padding: 12px;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
& .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
|
||||
@@ -1,52 +1,74 @@
|
||||
import { useEffect } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { IoClose } from 'react-icons/io5/index';
|
||||
|
||||
import { Portal } from '@/components/portal';
|
||||
|
||||
import { fade, mix, slideY } from '@/lib/motion';
|
||||
import { cn } from '@/helpers/styles';
|
||||
|
||||
import styles from './modal.module.css';
|
||||
|
||||
interface ModalProps {
|
||||
children: React.ReactNode;
|
||||
lockBody?: boolean;
|
||||
onClose: () => void;
|
||||
show: boolean;
|
||||
wide?: boolean;
|
||||
}
|
||||
|
||||
export function Modal({ children, onClose, show }: ModalProps) {
|
||||
export function Modal({
|
||||
children,
|
||||
lockBody = true,
|
||||
onClose,
|
||||
show,
|
||||
wide,
|
||||
}: ModalProps) {
|
||||
const variants = {
|
||||
modal: mix(fade(), slideY(20)),
|
||||
overlay: fade(),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (show && lockBody) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else if (lockBody) {
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
}, [show, lockBody]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
<>
|
||||
<motion.div
|
||||
animate="show"
|
||||
className={styles.overlay}
|
||||
exit="hidden"
|
||||
initial="hidden"
|
||||
variants={variants.overlay}
|
||||
onClick={onClose}
|
||||
onKeyDown={onClose}
|
||||
/>
|
||||
<div className={styles.modal}>
|
||||
<Portal>
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
<>
|
||||
<motion.div
|
||||
animate="show"
|
||||
className={styles.content}
|
||||
className={styles.overlay}
|
||||
exit="hidden"
|
||||
initial="hidden"
|
||||
variants={variants.modal}
|
||||
>
|
||||
<button className={styles.close} onClick={onClose}>
|
||||
<IoClose />
|
||||
</button>
|
||||
variants={variants.overlay}
|
||||
onClick={onClose}
|
||||
onKeyDown={onClose}
|
||||
/>
|
||||
<div className={styles.modal}>
|
||||
<motion.div
|
||||
animate="show"
|
||||
className={cn(styles.content, wide && styles.wide)}
|
||||
exit="hidden"
|
||||
initial="hidden"
|
||||
variants={variants.modal}
|
||||
>
|
||||
<button className={styles.close} onClick={onClose}>
|
||||
<IoClose />
|
||||
</button>
|
||||
|
||||
{children}
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{children}
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
1
src/components/modals/presets/index.ts
Normal file
1
src/components/modals/presets/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { PresetsModal } from './presets';
|
||||
1
src/components/modals/presets/list/index.ts
Normal file
1
src/components/modals/presets/list/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { List } from './list';
|
||||
62
src/components/modals/presets/list/list.module.css
Normal file
62
src/components/modals/presets/list/list.module.css
Normal file
@@ -0,0 +1,62 @@
|
||||
.list {
|
||||
& .title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
|
||||
& .empty {
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
& .preset {
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
padding: 4px;
|
||||
margin-top: 8px;
|
||||
background-color: var(--color-neutral-50);
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 8px;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
& input {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
color: var(--color-foreground);
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
& button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground-subtle);
|
||||
cursor: pointer;
|
||||
background-color: var(--color-neutral-100);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
|
||||
&.primary {
|
||||
font-size: var(--font-xsm);
|
||||
color: var(--color-foreground);
|
||||
background-color: var(--color-neutral-200);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/components/modals/presets/list/list.tsx
Normal file
53
src/components/modals/presets/list/list.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { FaPlay, FaRegTrashAlt } from 'react-icons/fa/index';
|
||||
|
||||
import styles from './list.module.css';
|
||||
|
||||
import { usePresetStore, useSoundStore } from '@/store';
|
||||
|
||||
interface ListProps {
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export function List({ close }: ListProps) {
|
||||
const presets = usePresetStore(state => state.presets);
|
||||
const changeName = usePresetStore(state => state.changeName);
|
||||
const deletePreset = usePresetStore(state => state.deletePreset);
|
||||
const override = useSoundStore(state => state.override);
|
||||
const play = useSoundStore(state => state.play);
|
||||
|
||||
return (
|
||||
<div className={styles.list}>
|
||||
<h3 className={styles.title}>
|
||||
Your Presets {presets.length > 0 && `(${presets.length})`}
|
||||
</h3>
|
||||
|
||||
{!presets.length && (
|
||||
<p className={styles.empty}>You don't have any presets yet.</p>
|
||||
)}
|
||||
|
||||
{presets.map((preset, index) => (
|
||||
<div className={styles.preset} key={index}>
|
||||
<input
|
||||
placeholder="Untitled"
|
||||
type="text"
|
||||
value={preset.label}
|
||||
onChange={e => changeName(index, e.target.value)}
|
||||
/>
|
||||
<button onClick={() => deletePreset(index)}>
|
||||
<FaRegTrashAlt />
|
||||
</button>
|
||||
<button
|
||||
className={styles.primary}
|
||||
onClick={() => {
|
||||
override(preset.sounds);
|
||||
play();
|
||||
close();
|
||||
}}
|
||||
>
|
||||
<FaPlay />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
src/components/modals/presets/new/index.ts
Normal file
1
src/components/modals/presets/new/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { New } from './new';
|
||||
62
src/components/modals/presets/new/new.module.css
Normal file
62
src/components/modals/presets/new/new.module.css
Normal file
@@ -0,0 +1,62 @@
|
||||
.new {
|
||||
margin-top: 16px;
|
||||
|
||||
& .title {
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
|
||||
& .form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
padding: 4px;
|
||||
margin-top: 8px;
|
||||
background-color: var(--color-neutral-50);
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 8px;
|
||||
|
||||
&.disabled {
|
||||
filter: blur(2px);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
& input {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
color: var(--color-foreground);
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
& button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-neutral-50);
|
||||
cursor: pointer;
|
||||
background-color: var(--color-neutral-950);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .noSelected {
|
||||
margin-top: 8px;
|
||||
font-size: var(--font-sm);
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
}
|
||||
59
src/components/modals/presets/new/new.tsx
Normal file
59
src/components/modals/presets/new/new.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useState, type FormEvent } from 'react';
|
||||
|
||||
import { cn } from '@/helpers/styles';
|
||||
import { useSoundStore, usePresetStore } from '@/store';
|
||||
|
||||
import styles from './new.module.css';
|
||||
|
||||
export function New() {
|
||||
const [name, setName] = useState('');
|
||||
|
||||
const noSelected = useSoundStore(state => state.noSelected());
|
||||
const sounds = useSoundStore(state => state.sounds);
|
||||
const addPreset = usePresetStore(state => state.addPreset);
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!name || noSelected) return;
|
||||
|
||||
const _sounds: Record<string, number> = {};
|
||||
|
||||
Object.keys(sounds)
|
||||
.filter(id => sounds[id].isSelected)
|
||||
.forEach(id => {
|
||||
_sounds[id] = sounds[id].volume;
|
||||
});
|
||||
|
||||
addPreset(name, _sounds);
|
||||
|
||||
setName('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.new}>
|
||||
<h3 className={styles.title}>New Preset</h3>
|
||||
|
||||
<form
|
||||
className={cn(styles.form, noSelected && styles.disabled)}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<input
|
||||
disabled={noSelected}
|
||||
placeholder="Preset's Name"
|
||||
required
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
/>
|
||||
<button disabled={noSelected}>Save</button>
|
||||
</form>
|
||||
|
||||
{noSelected && (
|
||||
<p className={styles.noSelected}>
|
||||
To make a preset, first select some sounds.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
src/components/modals/presets/presets.module.css
Normal file
12
src/components/modals/presets/presets.module.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--font-md);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
margin: 16px 0;
|
||||
background-color: var(--color-neutral-200);
|
||||
}
|
||||
21
src/components/modals/presets/presets.tsx
Normal file
21
src/components/modals/presets/presets.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Modal } from '@/components/modal';
|
||||
import { New } from './new';
|
||||
import { List } from './list';
|
||||
|
||||
import styles from './presets.module.css';
|
||||
|
||||
interface PresetsModalProps {
|
||||
onClose: () => void;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
export function PresetsModal({ onClose, show }: PresetsModalProps) {
|
||||
return (
|
||||
<Modal show={show} onClose={onClose}>
|
||||
<h2 className={styles.title}>Presets</h2>
|
||||
<New />
|
||||
<div className={styles.divider} />
|
||||
<List close={onClose} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
& input {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
padding: 0 10px;
|
||||
font-size: var(--font-sm);
|
||||
|
||||
@@ -23,7 +23,7 @@ export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) {
|
||||
.map(sound => ({
|
||||
id: sound,
|
||||
isSelected: sounds[sound].isSelected,
|
||||
volume: sounds[sound].volume.toFixed(1),
|
||||
volume: sounds[sound].volume.toFixed(2),
|
||||
}))
|
||||
.filter(sound => sound.isSelected);
|
||||
}, [sounds, JSON.stringify(sounds)]); // eslint-disable-line
|
||||
|
||||
1
src/components/portal/index.ts
Normal file
1
src/components/portal/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Portal } from './portal';
|
||||
14
src/components/portal/portal.tsx
Normal file
14
src/components/portal/portal.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
interface PortalProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Portal({ children }: PortalProps) {
|
||||
const [isClientSide, setIsClientSide] = useState(false);
|
||||
|
||||
useEffect(() => setIsClientSide(true), []);
|
||||
|
||||
return isClientSide ? createPortal(children, document.body) : null;
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
.button {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -31,7 +31,7 @@ export function ScrollToTop() {
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isVisible && (
|
||||
{isVisible ? (
|
||||
<motion.button
|
||||
animate="show"
|
||||
aria-label="Scroll to top"
|
||||
@@ -43,6 +43,8 @@ export function ScrollToTop() {
|
||||
>
|
||||
<BiUpArrowAlt />
|
||||
</motion.button>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
---
|
||||
import { Container } from '@/components/container';
|
||||
import { count as soundCount } from '@/lib/sounds';
|
||||
|
||||
const count = soundCount();
|
||||
---
|
||||
|
||||
<div class="about">
|
||||
<Container>
|
||||
<div class="titleWrapper">
|
||||
<h2 class="title">What is Moodist?</h2>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<p class="desc">
|
||||
Welcome to Moodist – your free, open-source ambient sound generator. With <span
|
||||
>{count} curated sounds</span
|
||||
>, effortlessly create your custom mix for focus or relaxation. No
|
||||
accounts, no hassle – just pure tranquility. Explore nature's calm
|
||||
and urban rhythms. Elevate your ambiance with Moodist, where simplicity
|
||||
meets serenity.
|
||||
</p>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.about {
|
||||
padding: 80px 0;
|
||||
|
||||
& .titleWrapper {
|
||||
display: flex;
|
||||
column-gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
& .title {
|
||||
margin-bottom: 12px;
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--font-lg);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
& .line {
|
||||
flex-grow: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-neutral-300),
|
||||
transparent
|
||||
);
|
||||
transform: translateY(-0.25rem);
|
||||
}
|
||||
}
|
||||
|
||||
& .desc {
|
||||
line-height: 1.7;
|
||||
color: var(--color-foreground-subtle);
|
||||
|
||||
& span {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
import { RiSparkling2Line } from 'react-icons/ri/index';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
---
|
||||
|
||||
<div class="ready">
|
||||
<Container>
|
||||
<div class="wrapper">
|
||||
<div class="iconContainer">
|
||||
<div class="tail"></div>
|
||||
<div class="icon">
|
||||
<RiSparkling2Line />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="title">Are you ready?</h2>
|
||||
<p class="desc">Create your calm oasis in seconds!</p>
|
||||
<button class="button" id="button"> Use Moodist</button>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const button = document.getElementById('button');
|
||||
|
||||
button?.addEventListener('click', () => {
|
||||
const app = document.getElementById('app');
|
||||
|
||||
app?.scrollIntoView(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.ready {
|
||||
& .wrapper {
|
||||
position: relative;
|
||||
padding: 0 20px 40px;
|
||||
background: linear-gradient(transparent, var(--color-neutral-100));
|
||||
border-radius: 0 0 20px 20px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 70%;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--color-neutral-300),
|
||||
transparent
|
||||
);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
& .iconContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
& .tail {
|
||||
width: 1px;
|
||||
height: 75px;
|
||||
background: linear-gradient(transparent, var(--color-neutral-300));
|
||||
}
|
||||
|
||||
& .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
font-size: var(--font-md);
|
||||
background-color: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
& .title {
|
||||
margin-bottom: 12px;
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--font-lg);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .desc {
|
||||
color: var(--color-foreground-subtle);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
margin: 24px auto 0;
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--font-sm);
|
||||
line-height: 0;
|
||||
color: var(--color-neutral-200);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
background-color: var(--color-neutral-950);
|
||||
border: none;
|
||||
border-radius: 100px;
|
||||
outline: none;
|
||||
transition: 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-neutral-800);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,157 +0,0 @@
|
||||
---
|
||||
import { BiMoney, BiUserCircle, BiLogoGithub } from 'react-icons/bi/index';
|
||||
import { BsSoundwave, BsStars } from 'react-icons/bs/index';
|
||||
import { RxMixerHorizontal } from 'react-icons/rx/index';
|
||||
|
||||
import { Balancer } from 'react-wrap-balancer';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
import { count as soundCount } from '@/lib/sounds';
|
||||
|
||||
const count = soundCount();
|
||||
|
||||
const reasons = [
|
||||
{
|
||||
Icon: BiMoney,
|
||||
body: "Immerse yourself in Moodist's ambient world without spending a dime. All features are accessible to everyone, ensuring a cost-free auditory journey.",
|
||||
id: 'free-access',
|
||||
label: 'Free Access',
|
||||
},
|
||||
{
|
||||
Icon: BiUserCircle,
|
||||
body: 'Embrace simplicity – Moodist skips the registration process. No accounts, no hassle; just click, play, and enjoy the serenity.',
|
||||
id: 'no-registration',
|
||||
label: 'No Registration',
|
||||
},
|
||||
{
|
||||
Icon: BsSoundwave,
|
||||
body: `With a curated collection of ${count} sounds, Moodist offers a spectrum of auditory experiences. From the tranquility of nature to the beat of urban life, find the perfect backdrop for your mood.`,
|
||||
id: 'diverse-sounds',
|
||||
label: 'Diverse Sounds',
|
||||
},
|
||||
{
|
||||
Icon: RxMixerHorizontal,
|
||||
body: 'Tailor your ambiance effortlessly. Moodist allows you to create personalized mixes, adjusting the blend of sounds to suit your focus or relaxation needs.',
|
||||
id: 'customizable-mixes',
|
||||
label: 'Customizable Mixes',
|
||||
},
|
||||
{
|
||||
Icon: BiLogoGithub,
|
||||
body: 'Trust in transparency. Moodist is open-source, fostering collaboration and providing users with a platform they can explore and understand.',
|
||||
id: 'open-source',
|
||||
label: 'Open-Source',
|
||||
link: {
|
||||
label: 'Source Code',
|
||||
url: 'https://github.com/remvze/moodist',
|
||||
},
|
||||
},
|
||||
{
|
||||
Icon: BsStars,
|
||||
body: 'Navigate with ease. Moodist provides a user-friendly interface, ensuring a smooth and hassle-free experience as you explore the diverse soundscape of calm and rhythm.',
|
||||
id: 'seamless-experience',
|
||||
label: 'Seamless Experience',
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<div class="why">
|
||||
<Container>
|
||||
<div class="titleWrapper">
|
||||
<h2 class="title">Why use Moodist?</h2>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
|
||||
<div class="reasons">
|
||||
{
|
||||
reasons.map(reason => (
|
||||
<div class="reason">
|
||||
<div class="icon">
|
||||
<reason.Icon />
|
||||
</div>
|
||||
<h3 class="label">{reason.label}</h3>
|
||||
<p class="body">
|
||||
<Balancer>{reason.body}</Balancer>
|
||||
</p>
|
||||
{reason.link && (
|
||||
<a class="link" href={reason.link.url}>
|
||||
{reason.link.label} →
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.why {
|
||||
padding-bottom: 80px;
|
||||
|
||||
& .titleWrapper {
|
||||
display: flex;
|
||||
column-gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
& .title {
|
||||
margin-bottom: 12px;
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--font-lg);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
& .line {
|
||||
flex-grow: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-neutral-300),
|
||||
transparent
|
||||
);
|
||||
transform: translateY(-0.25rem);
|
||||
}
|
||||
}
|
||||
|
||||
& .reasons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
row-gap: 28px;
|
||||
column-gap: 20px;
|
||||
margin-top: 24px;
|
||||
|
||||
& .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: 12px;
|
||||
font-size: var(--font-md);
|
||||
color: var(--color-foreground-subtle);
|
||||
background: linear-gradient(var(--color-neutral-100), transparent);
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
& .label {
|
||||
margin-bottom: 8px;
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
& .body {
|
||||
line-height: 1.6;
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
|
||||
& .link {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -91,7 +91,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
& h3 {
|
||||
& .label {
|
||||
margin-top: 8px;
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--font-sm);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Range } from './range';
|
||||
import { Favorite } from './favorite';
|
||||
|
||||
import { useSound } from '@/hooks/use-sound';
|
||||
import { useSoundStore } from '@/store';
|
||||
import { useSoundStore, useLoadingStore } from '@/store';
|
||||
import { cn } from '@/helpers/styles';
|
||||
|
||||
import styles from './sound.module.css';
|
||||
@@ -37,6 +37,8 @@ export function Sound({
|
||||
const volume = useSoundStore(state => state.sounds[id].volume);
|
||||
const isSelected = useSoundStore(state => state.sounds[id].isSelected);
|
||||
|
||||
const isLoading = useLoadingStore(state => state.loaders[src]);
|
||||
|
||||
const sound = useSound(src, { loop: true, volume });
|
||||
|
||||
useEffect(() => {
|
||||
@@ -80,7 +82,7 @@ export function Sound({
|
||||
>
|
||||
<Favorite id={id} />
|
||||
<div className={styles.icon}>
|
||||
{sound.isLoading ? (
|
||||
{isLoading ? (
|
||||
<span className={styles.spinner}>
|
||||
<ImSpinner9 />
|
||||
</span>
|
||||
@@ -88,7 +90,9 @@ export function Sound({
|
||||
icon
|
||||
)}
|
||||
</div>
|
||||
<h3 id={id}>{label}</h3>
|
||||
<div className={styles.label} id={id}>
|
||||
{label}
|
||||
</div>
|
||||
<Range id={id} />
|
||||
</div>
|
||||
);
|
||||
|
||||
1
src/components/source/index.ts
Normal file
1
src/components/source/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Source } from './source';
|
||||
70
src/components/source/source.module.css
Normal file
70
src/components/source/source.module.css
Normal file
@@ -0,0 +1,70 @@
|
||||
.source {
|
||||
/* margin-top: 80px; */
|
||||
|
||||
margin-top: 40px;
|
||||
|
||||
& .wrapper {
|
||||
position: relative;
|
||||
padding: 0 20px 40px;
|
||||
background: linear-gradient(transparent, rgb(24 24 27 / 70%));
|
||||
border-radius: 0 0 20px 20px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 70%;
|
||||
height: 1px;
|
||||
content: '';
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--color-neutral-400),
|
||||
transparent
|
||||
);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
& .iconContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
& .tail {
|
||||
width: 1px;
|
||||
height: 75px;
|
||||
background: linear-gradient(transparent, var(--color-neutral-300));
|
||||
}
|
||||
|
||||
& .icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
font-size: var(--font-md);
|
||||
background-color: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
& .title {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--font-lg);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .desc {
|
||||
margin-top: 8px;
|
||||
color: var(--color-foreground-subtle);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 16px auto 0;
|
||||
}
|
||||
}
|
||||
32
src/components/source/source.tsx
Normal file
32
src/components/source/source.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { FaGithub } from 'react-icons/fa/index';
|
||||
|
||||
import { Container } from '@/components/container';
|
||||
import { SpecialButton } from '@/components/special-button';
|
||||
|
||||
import styles from './source.module.css';
|
||||
|
||||
export function Source() {
|
||||
return (
|
||||
<div className={styles.source}>
|
||||
<Container>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.iconContainer}>
|
||||
<div className={styles.tail} />
|
||||
<div className={styles.icon}>
|
||||
<FaGithub />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className={styles.title}>Open Source</h2>
|
||||
<p className={styles.desc}>Moodist is free and open-source!</p>
|
||||
<SpecialButton
|
||||
className={styles.button}
|
||||
href="https://github.com/remvze/moodist"
|
||||
>
|
||||
Source Code
|
||||
</SpecialButton>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
src/components/special-button/index.ts
Normal file
1
src/components/special-button/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { SpecialButton } from './special-button';
|
||||
74
src/components/special-button/special-button.module.css
Normal file
74
src/components/special-button/special-button.module.css
Normal file
@@ -0,0 +1,74 @@
|
||||
.button {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: max-content;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
overflow: hidden;
|
||||
font-size: var(--font-xsm);
|
||||
font-weight: 500;
|
||||
color: var(--color-neutral-subtle);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
background-color: var(--color-neutral-200);
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
transition: 0.2s;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
z-index: -1;
|
||||
width: calc(100% - 2px);
|
||||
height: calc(100% - 2px);
|
||||
content: '';
|
||||
background-color: var(--color-neutral-50);
|
||||
border-radius: 100px;
|
||||
transition: inherit;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: -2;
|
||||
width: 150%;
|
||||
aspect-ratio: 1 / 1;
|
||||
content: '';
|
||||
background-image: conic-gradient(
|
||||
transparent,
|
||||
transparent,
|
||||
var(--color-neutral-400),
|
||||
transparent,
|
||||
transparent,
|
||||
transparent,
|
||||
transparent,
|
||||
var(--color-neutral-400),
|
||||
transparent,
|
||||
transparent
|
||||
);
|
||||
transform: translate(-50%, -50%);
|
||||
animation-name: shine;
|
||||
animation-duration: 6s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
background-color: var(--color-neutral-100);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(450deg);
|
||||
}
|
||||
}
|
||||
26
src/components/special-button/special-button.tsx
Normal file
26
src/components/special-button/special-button.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { cn } from '@/helpers/styles';
|
||||
|
||||
import styles from './special-button.module.css';
|
||||
|
||||
interface SpecialButtonProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export function SpecialButton({
|
||||
children,
|
||||
className,
|
||||
href,
|
||||
}: SpecialButtonProps) {
|
||||
return (
|
||||
<a
|
||||
className={cn(styles.button, className)}
|
||||
href={href}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useSoundStore } from '@/store';
|
||||
import { useSoundStore, useNoteStore, usePresetStore } from '@/store';
|
||||
|
||||
interface StoreConsumerProps {
|
||||
children: React.ReactNode;
|
||||
@@ -9,6 +9,8 @@ interface StoreConsumerProps {
|
||||
export function StoreConsumer({ children }: StoreConsumerProps) {
|
||||
useEffect(() => {
|
||||
useSoundStore.persist.rehydrate();
|
||||
useNoteStore.persist.rehydrate();
|
||||
usePresetStore.persist.rehydrate();
|
||||
}, []);
|
||||
|
||||
return <>{children}</>;
|
||||
|
||||
1
src/components/toolbar/index.ts
Normal file
1
src/components/toolbar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Toolbar } from './toolbar';
|
||||
13
src/components/toolbar/toolbar.module.css
Normal file
13
src/components/toolbar/toolbar.module.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.wrapper {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 0;
|
||||
z-index: 15;
|
||||
width: 100%;
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
16
src/components/toolbar/toolbar.tsx
Normal file
16
src/components/toolbar/toolbar.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Container } from '@/components/container';
|
||||
import { Menu } from '@/components/menu';
|
||||
import { ScrollToTop } from '@/components/scroll-to-top';
|
||||
|
||||
import styles from './toolbar.module.css';
|
||||
|
||||
export function Toolbar() {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Container className={styles.container} wide>
|
||||
<ScrollToTop />
|
||||
<Menu />
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user