40 Commits

Author SHA1 Message Date
MAZE
b8bc9c8b4c chore(release): 1.2.0 2024-01-04 20:19:35 +03:30
MAZE
ee606139a8 chore: update GitHub action 2024-01-04 20:18:45 +03:30
MAZE
7823dc7ff4 style: add animation to modal 2024-01-04 19:58:16 +03:30
MAZE
37a0736a0e style: widen the menu 2024-01-04 19:54:38 +03:30
MAZE
c51acd6261 style: change copy 2024-01-04 19:54:06 +03:30
MAZE
ff26597d22 feat: add disabled state 2024-01-04 19:51:58 +03:30
MAZE
c8e51226e5 style: change to primary color 2024-01-03 20:03:32 +03:30
MAZE
131ab29621 style: add icon to menu items 2024-01-03 20:01:58 +03:30
MAZE
0f62f0795c feat: implement override feature 2024-01-03 18:42:06 +03:30
MAZE
1a23e004a6 fix: stringify dependency 2024-01-03 14:14:01 +03:30
MAZE
93ff72a052 feat: implement sharing URL 2024-01-03 00:03:58 +03:30
MAZE
ef81f198ba feat: basic structure for share link 2024-01-02 22:20:59 +03:30
MAZE
35e32152b1 feat: add share modal 2024-01-02 19:48:57 +03:30
MAZE
26bf01690c refactor: better item structure for menu 2024-01-02 18:35:06 +03:30
MAZE
fe2357c995 feat: add share placeholder 2024-01-02 17:35:10 +03:30
MAZE
85b627ecb9 style: change border color 2024-01-02 16:58:41 +03:30
MAZE
8beb42cb1b refactor: rewrite menu with floating ui 2024-01-02 16:54:29 +03:30
MAZE
17027e299b feat: add animation to menu box 2024-01-02 16:28:18 +03:30
MAZE
184bb09f5a feat: add menu button 2024-01-02 16:24:55 +03:30
MAZE
660ee07a23 chore: change docker-compose file 2024-01-01 12:42:56 +03:30
MAZE
cb4bfea5ab chore: change docker workflow 2024-01-01 12:39:22 +03:30
MAZE ✧
210bd234e0 Merge pull request #3 from baldator/main
Add github action to build docker image and docker-compose support
2024-01-01 12:32:17 +03:30
marco
64ef5c5138 . 2023-12-31 13:43:27 +00:00
MAZE ✧
1218751a6f Merge pull request #2 from javigomezo/main
feat(docker): add dockerfile
2023-12-31 12:57:16 +03:30
Javier Gómez
a234bc17a6 feat(docker): add dockerfile 2023-12-30 23:07:10 +01:00
MAZE
69b85199bb chore(release): 1.1.0 2023-12-29 17:32:03 +03:30
MAZE
2c74dd0d60 style: add hover states 2023-12-29 17:30:13 +03:30
MAZE
8efb1cee00 style: change button style 2023-12-29 17:22:41 +03:30
MAZE
bd517f88c0 style: change theme 2023-12-29 17:15:34 +03:30
MAZE
00fc5f3a87 style: change sound counter 2023-12-28 22:44:53 +03:30
MAZE
37bad8149e chore: relocate underwater audio 2023-12-28 22:19:23 +03:30
MAZE
c1c3945d43 chore: add transport category 2023-12-28 22:16:28 +03:30
MAZE
5970012fa6 chore: add places category 2023-12-28 22:09:02 +03:30
MAZE
121a8f204c chore: remove heartbeat audio 2023-12-28 21:53:16 +03:30
MAZE
f43a378697 chore: change heartbeat audio 2023-12-28 21:45:20 +03:30
MAZE
28c3c404ad fix: change icon path 2023-12-28 00:06:19 +03:30
MAZE
e3864bede1 chore: add more sounds 2023-12-28 00:02:50 +03:30
MAZE
55e7f05892 chore: add more sounds 2023-12-27 23:40:29 +03:30
MAZE
318e87c9f1 chore: add more sounds 2023-12-27 16:55:22 +03:30
MAZE
a43c679e21 refactor: change ordering config 2023-12-10 15:37:37 +03:30
79 changed files with 1218 additions and 239 deletions

34
.github/workflows/build_docker.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Build and push main image
on:
push:
tags:
- '*'
jobs:
push-store-image:
runs-on: ubuntu-latest
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@main
- name: 'Login to GitHub Container Registry'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{github.actor}}
password: ${{secrets.ACCESS_TOKEN}}
- name: 'Build 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

View File

@@ -1,7 +1,7 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-idiomatic-order",
"stylelint-config-recess-order",
"stylelint-config-html",
"stylelint-prettier/recommended"
],

View File

@@ -11,8 +11,8 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"[javascript][javascriptreact][typescript][typescriptreact][astro]": {
"editor.formatOnSave": false

View File

@@ -2,6 +2,81 @@
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.2.0](https://github.com/remvze/moodist/compare/v1.1.0...v1.2.0) (2024-01-04)
### ♻️ Code Refactoring
* better item structure for menu ([26bf016](https://github.com/remvze/moodist/commit/26bf01690cfcc105b661951bcb2347394a67fb68))
* rewrite menu with floating ui ([8beb42c](https://github.com/remvze/moodist/commit/8beb42cb1b92c99aa9656b35cd7d82094e5baf72))
### 🐛 Bug Fixes
* stringify dependency ([1a23e00](https://github.com/remvze/moodist/commit/1a23e004a65960ce169990211f150db25762fead))
### ✨ Features
* add animation to menu box ([17027e2](https://github.com/remvze/moodist/commit/17027e299bb9bf958aebaf735c40e7664ad71e8b))
* add disabled state ([ff26597](https://github.com/remvze/moodist/commit/ff26597d22d444d18d2874a5c278eccc288972de))
* add menu button ([184bb09](https://github.com/remvze/moodist/commit/184bb09f5ab09fcf877e6a904023d9de72be9a89))
* add share modal ([35e3215](https://github.com/remvze/moodist/commit/35e32152b153f4dfaf9e071f526f6d7602ea97fc))
* add share placeholder ([fe2357c](https://github.com/remvze/moodist/commit/fe2357c995713cd0fb8335b325266859dc47a769))
* basic structure for share link ([ef81f19](https://github.com/remvze/moodist/commit/ef81f198baeb927e3b1768570f75e6638a7bd0b6))
* **docker:** add dockerfile ([a234bc1](https://github.com/remvze/moodist/commit/a234bc17a66331acbbc1d980cd1f53d58646f534))
* implement override feature ([0f62f07](https://github.com/remvze/moodist/commit/0f62f0795c5a9e06fa4e62b6b7b1e6c0774dfe0f))
* implement sharing URL ([93ff72a](https://github.com/remvze/moodist/commit/93ff72a052484b36c9ac821b94b632865b4a3550))
### 💄 Styling
* add animation to modal ([7823dc7](https://github.com/remvze/moodist/commit/7823dc7ff473278ef8ee401e69796c17b33da794))
* add icon to menu items ([131ab29](https://github.com/remvze/moodist/commit/131ab296215812e45a0c60486d75683f3de25d16))
* change border color ([85b627e](https://github.com/remvze/moodist/commit/85b627ecb96a4f52ecacdb53ed4484c050adba5e))
* change copy ([c51acd6](https://github.com/remvze/moodist/commit/c51acd62618cc705902dc01f0574a2c9124264c5))
* change to primary color ([c8e5122](https://github.com/remvze/moodist/commit/c8e51226e57bfa72ad91318de25fc5f9b5751634))
* widen the menu ([37a0736](https://github.com/remvze/moodist/commit/37a0736a0e7edd09c33940099c884e5b48afbbf1))
### 🚚 Chores
* change docker workflow ([cb4bfea](https://github.com/remvze/moodist/commit/cb4bfea5ab4326dee17c78554f12a08ffcb9dd0e))
* change docker-compose file ([660ee07](https://github.com/remvze/moodist/commit/660ee07a2359ec77c9d56bbe552541246e0f79c5))
* update GitHub action ([ee60613](https://github.com/remvze/moodist/commit/ee606139a80121fd6ee1b8233f82af994c4e1178))
## [1.1.0](https://github.com/remvze/moodist/compare/v1.0.0...v1.1.0) (2023-12-29)
### ♻️ Code Refactoring
* change ordering config ([a43c679](https://github.com/remvze/moodist/commit/a43c679e214b24c7f547e182aea6e2fbf826228f))
### 🐛 Bug Fixes
* change icon path ([28c3c40](https://github.com/remvze/moodist/commit/28c3c404ad790869b13731e4c3622abe33f1dda2))
### 🚚 Chores
* add more sounds ([e3864be](https://github.com/remvze/moodist/commit/e3864bede129c102ef5b7258b4688d9177dd284c))
* add more sounds ([55e7f05](https://github.com/remvze/moodist/commit/55e7f05892f6d3200b56a7e06b371bed4b4c4554))
* add more sounds ([318e87c](https://github.com/remvze/moodist/commit/318e87c9f1f3e2509c2b8eeb3a7f6875dd1c02fd))
* add places category ([5970012](https://github.com/remvze/moodist/commit/5970012fa6cbd8222c2be8ce426065f928d81b2b))
* add transport category ([c1c3945](https://github.com/remvze/moodist/commit/c1c3945d43e84e3011de52bffa5116d58283c473))
* change heartbeat audio ([f43a378](https://github.com/remvze/moodist/commit/f43a378697437f671c0c33122b1c9ec5a1e173ff))
* relocate underwater audio ([37bad81](https://github.com/remvze/moodist/commit/37bad8149e1f5170426dc745322c0e65cb9a41ff))
* remove heartbeat audio ([121a8f2](https://github.com/remvze/moodist/commit/121a8f204c6b61490a7ab0e732779031278e6e8c))
### 💄 Styling
* add hover states ([2c74dd0](https://github.com/remvze/moodist/commit/2c74dd0d604af86f99edcba2eb573641ac2010fd))
* change button style ([8efb1ce](https://github.com/remvze/moodist/commit/8efb1cee00ec0e0dcd9677729d9136ca8d69073f))
* change sound counter ([00fc5f3](https://github.com/remvze/moodist/commit/00fc5f3a872be51eb875744e254c75ea58e93281))
* change theme ([bd517f8](https://github.com/remvze/moodist/commit/bd517f88c01202eb7e7e5acf70bf4af2e6f91d75))
## [1.0.0](https://github.com/remvze/moodist/compare/v0.0.1...v1.0.0) (2023-12-09)

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM node:20-alpine3.18 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine AS runtime
COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 8080

10
docker-compose.yml Normal file
View File

@@ -0,0 +1,10 @@
version: '3.9'
services:
moodist:
image: ghcr.io/remvze/moodist
logging:
options:
max-size: 1g
restart: always
ports:
- '8080:8080'

31
docker/nginx/nginx.conf Normal file
View File

@@ -0,0 +1,31 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html index.htm;
include /etc/nginx/mime.types;
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
internal;
}
location / {
try_files $uri $uri/index.html =404;
}
}
}

65
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "moodist",
"version": "1.0.0",
"version": "1.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "moodist",
"version": "1.0.0",
"version": "1.2.0",
"dependencies": {
"@astrojs/react": "^3.0.3",
"@floating-ui/react": "0.26.0",
@@ -54,7 +54,7 @@
"standard-version": "9.5.0",
"stylelint": "15.10.3",
"stylelint-config-html": "1.1.0",
"stylelint-config-idiomatic-order": "9.0.0",
"stylelint-config-recess-order": "4.4.0",
"stylelint-config-standard": "34.0.0",
"stylelint-prettier": "4.0.2"
}
@@ -12590,15 +12590,6 @@
"node": ">=4"
}
},
"node_modules/postcss-sorting": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-7.0.1.tgz",
"integrity": "sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==",
"dev": true,
"peerDependencies": {
"postcss": "^8.3.9"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
@@ -14922,32 +14913,16 @@
"stylelint": ">=14.0.0"
}
},
"node_modules/stylelint-config-idiomatic-order": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/stylelint-config-idiomatic-order/-/stylelint-config-idiomatic-order-9.0.0.tgz",
"integrity": "sha512-+LtfPycY1Paayf1MaERyh6BzVPnZxemX5NtzdUPqi4u8hyAR7859f/4EL02+Kr9va76iX7mbYC4HendocXKJZQ==",
"node_modules/stylelint-config-recess-order": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-4.4.0.tgz",
"integrity": "sha512-Q99kvZyIM/aoPEV4dRDkzD3fZLzH0LXi+pawCf1r700uUeF/PHQ5PZXjwFUuGrWhOzd1N+cuVm+OUGsY2fRN5A==",
"dev": true,
"dependencies": {
"stylelint-order": "^5.0.0"
},
"engines": {
"node": ">=12"
"stylelint-order": "6.x"
},
"peerDependencies": {
"stylelint": ">=11"
}
},
"node_modules/stylelint-config-idiomatic-order/node_modules/stylelint-order": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-5.0.0.tgz",
"integrity": "sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==",
"dev": true,
"dependencies": {
"postcss": "^8.3.11",
"postcss-sorting": "^7.0.1"
},
"peerDependencies": {
"stylelint": "^14.0.0"
"stylelint": ">=15"
}
},
"node_modules/stylelint-config-recommended": {
@@ -14977,6 +14952,28 @@
"stylelint": "^15.10.0"
}
},
"node_modules/stylelint-order": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz",
"integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==",
"dev": true,
"dependencies": {
"postcss": "^8.4.32",
"postcss-sorting": "^8.0.2"
},
"peerDependencies": {
"stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
}
},
"node_modules/stylelint-order/node_modules/postcss-sorting": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz",
"integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==",
"dev": true,
"peerDependencies": {
"postcss": "^8.4.20"
}
},
"node_modules/stylelint-prettier": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-4.0.2.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "moodist",
"type": "module",
"version": "1.0.0",
"version": "1.2.0",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
@@ -67,7 +67,7 @@
"standard-version": "9.5.0",
"stylelint": "15.10.3",
"stylelint-config-html": "1.1.0",
"stylelint-config-idiomatic-order": "9.0.0",
"stylelint-config-recess-order": "4.4.0",
"stylelint-config-standard": "34.0.0",
"stylelint-prettier": "4.0.2"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -9,7 +9,8 @@ import { StoreConsumer } from '@/components/store-consumer';
import { Buttons } from '@/components/buttons';
import { Categories } from '@/components/categories';
import { ScrollToTop } from '@/components/scroll-to-top';
import { Shuffle } from '@/components/shuffle';
import { SharedModal } from '@/components/modals/shared';
import { Menu } from '@/components/menu/menu';
import { SnackbarProvider } from '@/contexts/snackbar';
import { sounds } from '@/data/sounds';
@@ -60,7 +61,8 @@ export function App() {
</Container>
<ScrollToTop />
<Shuffle />
<Menu />
<SharedModal />
</StoreConsumer>
</SnackbarProvider>
);

View File

@@ -1,11 +1,11 @@
.buttons {
position: sticky;
z-index: 10;
top: 30px;
z-index: 10;
display: flex;
width: max-content;
column-gap: 10px;
align-items: center;
justify-content: center;
width: max-content;
margin: 0 auto;
column-gap: 10px;
}

View File

@@ -1,20 +1,23 @@
.playButton {
display: flex;
width: 150px;
height: 45px;
align-items: center;
justify-content: center;
border: none;
border-radius: 100px;
border-top: 2px solid #34d399;
border-bottom: 3px solid #059669;
background-color: #10b981;
color: #022c22;
cursor: pointer;
width: 150px;
height: 45px;
font-family: var(--font-heading);
font-size: var(--font-base);
line-height: 0;
color: var(--color-neutral-200);
cursor: pointer;
background-color: var(--color-neutral-950);
border: 1px solid var(--color-neutral-50);
border-radius: 100px;
outline: none;
transition: 0.2s;
&:hover {
background-color: var(--color-neutral-800);
}
&:disabled,
&.disabled {

View File

@@ -1,19 +1,17 @@
.unselectButton {
display: flex;
width: 45px;
height: 45px;
align-items: center;
justify-content: center;
border: none;
border-radius: 100px;
border-top: 2px solid #fb7185;
border-bottom: 3px solid #be123c;
background-color: #f43f5e;
color: var(--color-foreground);
cursor: pointer;
width: 45px;
height: 45px;
font-family: var(--font-heading);
font-size: var(--font-md);
line-height: 0;
color: var(--color-foreground);
cursor: pointer;
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-300);
border-radius: 100px;
outline: none;
transition: 0.2s;
@@ -22,18 +20,15 @@
cursor: not-allowed;
}
&.restore {
border-top-color: var(--color-neutral-950);
border-bottom-color: var(--color-neutral-600);
background-color: var(--color-neutral-700);
color: var(--color-neutral-200);
&:hover {
background-color: var(--color-neutral-200);
}
}
.tooltip {
padding: 6px 12px;
font-size: var(--font-xsm);
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-200);
border-radius: 100px;
background-color: var(--color-neutral-100);
font-size: var(--font-xsm);
}

View File

@@ -48,7 +48,6 @@ export function UnselectButton() {
}
className={cn(
styles.unselectButton,
hasHistory && styles.restore,
noSelected && !hasHistory && styles.disabled,
)}
onClick={() => {

View File

@@ -17,14 +17,14 @@
& .icon {
display: flex;
width: 45px;
height: 45px;
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%;
background-color: var(--color-neutral-100);
font-size: var(--font-md);
}
}

View File

@@ -16,17 +16,17 @@ import { Container } from '@/components/container';
<style>
.footer {
display: flex;
height: 100px;
align-items: center;
height: 100px;
& p {
color: var(--color-foreground-subtle);
font-size: var(--font-sm);
color: var(--color-foreground-subtle);
text-align: center;
& a {
color: var(--color-foreground);
font-weight: 500;
color: var(--color-foreground);
text-decoration: none;
& span {

View File

@@ -1,5 +1,6 @@
---
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';
@@ -27,7 +28,10 @@ const count = soundCount();
<Balancer>Ambient sounds for focus and calm.</Balancer>
</p>
<p class="sounds">{count} Sounds</p>
<p class="sounds">
<span class="icon"><BsSoundwave /></span>
<span>{count} Sounds</span>
</p>
</Container>
</div>
@@ -44,12 +48,12 @@ const count = soundCount();
& .title {
display: flex;
align-items: center;
column-gap: 15px;
align-items: center;
& div {
height: 1px;
flex-grow: 1;
height: 1px;
&.left {
background: linear-gradient(
@@ -77,22 +81,35 @@ const count = soundCount();
& .desc {
margin-top: 5px;
color: var(--color-foreground-subtle);
line-height: 1.6;
color: var(--color-foreground-subtle);
}
& .sounds {
position: relative;
display: flex;
width: max-content;
column-gap: 8px;
align-items: center;
justify-content: center;
padding: 6px 16px;
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;
margin: 20px auto 0;
background-color: var(--color-neutral-100);
font-size: var(--font-xsm);
& .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;
@@ -100,13 +117,13 @@ const count = soundCount();
left: 50%;
width: 70%;
height: 1px;
content: '';
background: linear-gradient(
90deg,
transparent,
var(--color-neutral-400),
transparent
);
content: '';
transform: translateX(-50%);
}
}

View File

@@ -0,0 +1 @@
export { Menu } from './menu';

View File

@@ -0,0 +1 @@
export { Item } from './item';

View File

@@ -0,0 +1,33 @@
.item {
display: flex;
column-gap: 8px;
align-items: center;
justify-content: flex-start;
width: 100%;
padding: 16px 12px;
font-size: var(--font-sm);
font-weight: 500;
line-height: 1;
color: var(--color-foreground-subtle);
text-align: left;
cursor: pointer;
background-color: transparent;
border: 1px solid var(--color-neutral-200);
border-radius: 4px;
outline: none;
transition: 0.2s;
&:disabled {
cursor: not-allowed;
opacity: 0.4;
}
&:not(:disabled):hover {
color: var(--color-foreground);
background-color: var(--color-neutral-200);
}
& .icon {
color: var(--color-foreground);
}
}

View File

@@ -0,0 +1,16 @@
import styles from './item.module.css';
interface ItemProps {
disabled: boolean;
icon: React.ReactElement;
label: string;
onClick: () => void;
}
export function Item({ disabled = false, icon, label, onClick }: ItemProps) {
return (
<button className={styles.item} disabled={disabled} onClick={onClick}>
<span className={styles.icon}>{icon}</span> {label}
</button>
);
}

View File

@@ -0,0 +1,2 @@
export { Shuffle as ShuffleItem } from './shuffle';
export { Share as ShareItem } from './share';

View File

@@ -0,0 +1,22 @@
import { IoShareSocialSharp } from 'react-icons/io5/index';
import { Item } from '../item';
import { useSoundStore } from '@/store';
interface ShareProps {
open: () => void;
}
export function Share({ open }: ShareProps) {
const noSelected = useSoundStore(state => state.noSelected());
return (
<Item
disabled={noSelected}
icon={<IoShareSocialSharp />}
label="Share Sounds"
onClick={open}
/>
);
}

View File

@@ -0,0 +1,11 @@
import { BiShuffle } from 'react-icons/bi/index';
import { useSoundStore } from '@/store';
import { Item } from '../item';
export function Shuffle() {
const shuffle = useSoundStore(state => state.shuffle);
return <Item icon={<BiShuffle />} label="Shuffle Sounds" onClick={shuffle} />;
}

View File

@@ -0,0 +1,57 @@
.wrapper {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 5;
& .menuButton {
display: flex;
align-items: center;
justify-content: center;
width: 45px;
height: 45px;
font-size: var(--font-md);
color: var(--color-foreground);
cursor: pointer;
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-300);
border-radius: 50%;
transition: 0.2s;
&:hover {
background-color: var(--color-neutral-200);
}
}
& .menu {
display: flex;
flex-direction: column;
row-gap: 4px;
width: 240px;
padding: 4px;
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);
}
}
}
}

View File

@@ -0,0 +1,92 @@
import { useState } from 'react';
import { IoMenu, IoClose } from 'react-icons/io5/index';
import { AnimatePresence, motion } from 'framer-motion';
import {
useFloating,
autoUpdate,
offset,
flip,
shift,
useClick,
useDismiss,
useRole,
useInteractions,
FloatingFocusManager,
} from '@floating-ui/react';
import { ShuffleItem, ShareItem } from './items';
import { ShareLinkModal } from '@/components/modals/share-link';
import { slideY, fade, mix } from '@/lib/motion';
import styles from './menu.module.css';
export function Menu() {
const [isOpen, setIsOpen] = useState(false);
const [showShareLink, setShowShareLink] = useState(false);
const variants = mix(slideY(-20), fade());
const { context, floatingStyles, refs } = useFloating({
middleware: [offset(12), flip(), shift()],
onOpenChange: setIsOpen,
open: isOpen,
placement: 'top-end',
whileElementsMounted: autoUpdate,
});
const click = useClick(context);
const dismiss = useDismiss(context);
const role = useRole(context);
const { getFloatingProps, getReferenceProps } = useInteractions([
click,
dismiss,
role,
]);
return (
<>
<div className={styles.wrapper}>
<button
aria-label="Menu"
className={styles.menuButton}
ref={refs.setReference}
onClick={() => setIsOpen(prev => !prev)}
{...getReferenceProps()}
>
{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>
</div>
<ShareLinkModal
show={showShareLink}
onClose={() => setShowShareLink(false)}
/>
</>
);
}

View File

@@ -0,0 +1 @@
export { Modal } from './modal';

View File

@@ -0,0 +1,49 @@
.overlay {
position: fixed;
inset: 0;
z-index: 10;
background-color: rgb(9 9 11 / 40%);
backdrop-filter: blur(5px);
}
.modal {
position: fixed;
top: 50%;
left: 50%;
z-index: 12;
width: 100%;
max-height: 100%;
padding: 50px 0;
overflow-y: auto;
pointer-events: none;
transform: translate(-50%, -50%);
& .content {
position: relative;
width: 90%;
max-width: 500px;
padding: 20px;
padding-top: 40px;
margin: 0 auto;
pointer-events: fill;
background-color: var(--color-neutral-100);
border-radius: 8px;
& .close {
position: absolute;
top: 10px;
right: 10px;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
font-size: 16px;
color: var(--color-foreground-subtle);
cursor: pointer;
background-color: transparent;
border: none;
outline: none;
}
}
}

View File

@@ -0,0 +1,52 @@
import { AnimatePresence, motion } from 'framer-motion';
import { IoClose } from 'react-icons/io5/index';
import { fade, mix, slideY } from '@/lib/motion';
import styles from './modal.module.css';
interface ModalProps {
children: React.ReactNode;
onClose: () => void;
show: boolean;
}
export function Modal({ children, onClose, show }: ModalProps) {
const variants = {
modal: mix(fade(), slideY(20)),
overlay: fade(),
};
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}>
<motion.div
animate="show"
className={styles.content}
exit="hidden"
initial="hidden"
variants={variants.modal}
>
<button className={styles.close} onClick={onClose}>
<IoClose />
</button>
{children}
</motion.div>
</div>
</>
)}
</AnimatePresence>
);
}

View File

@@ -0,0 +1 @@
export { ShareLinkModal } from './share-link';

View File

@@ -0,0 +1,53 @@
.heading {
font-family: var(--font-heading);
font-size: var(--font-md);
font-weight: 700;
}
.desc {
margin-top: 12px;
line-height: 1.6;
color: var(--color-foreground-subtle);
}
.inputWrapper {
display: flex;
align-items: center;
width: 100%;
height: 45px;
padding: 4px;
margin-top: 12px;
background-color: var(--color-neutral-50);
border: 1px solid var(--color-neutral-200);
border-radius: 4px;
& input {
flex-grow: 1;
height: 100%;
padding: 0 10px;
font-size: var(--font-sm);
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;
color: var(--color-foreground);
cursor: pointer;
background-color: var(--color-neutral-100);
border: none;
border-radius: 4px;
outline: none;
transition: 0.2s;
&:hover {
background-color: var(--color-neutral-200);
}
}
}

View File

@@ -0,0 +1,67 @@
import { useMemo, useEffect, useState } from 'react';
import { IoCopyOutline, IoCheckmark } from 'react-icons/io5/index';
import { Modal } from '@/components/modal';
import { useCopy } from '@/hooks/use-copy';
import { useSoundStore } from '@/store';
import styles from './share-link.module.css';
interface ShareLinkModalProps {
onClose: () => void;
show: boolean;
}
export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) {
const [isMounted, setIsMounted] = useState(false);
const sounds = useSoundStore(state => state.sounds);
const { copy, copying } = useCopy();
const selected = useMemo(() => {
return Object.keys(sounds)
.map(sound => ({
id: sound,
isSelected: sounds[sound].isSelected,
volume: sounds[sound].volume.toFixed(1),
}))
.filter(sound => sound.isSelected);
}, [sounds, JSON.stringify(sounds)]); // eslint-disable-line
const string = useMemo(() => {
const object: Record<string, number> = {};
selected.forEach(sound => {
object[sound.id] = Number(sound.volume);
});
return JSON.stringify(object);
}, [selected]);
const url = useMemo(() => {
if (!isMounted)
return `https://moodist.app/?share=${encodeURIComponent(string)}`;
return `${window.location.protocol}//${
window.location.host
}/?share=${encodeURIComponent(string)}`;
}, [string, isMounted]);
useEffect(() => setIsMounted(true), []);
return (
<Modal show={show} onClose={onClose}>
<h1 className={styles.heading}>Share your sound selection!</h1>
<p className={styles.desc}>
Copy and send the following link to the person you want to share your
selection with.
</p>
<div className={styles.inputWrapper}>
<input readOnly type="text" value={url} />
<button onClick={() => copy(url)}>
{copying ? <IoCheckmark /> : <IoCopyOutline />}
</button>
</div>
</Modal>
);
}

View File

@@ -0,0 +1 @@
export { SharedModal } from './shared';

View File

@@ -0,0 +1,68 @@
.heading {
font-family: var(--font-heading);
font-size: var(--font-md);
font-weight: 700;
}
.desc {
margin-top: 12px;
line-height: 1.6;
color: var(--color-foreground-subtle);
}
.sounds {
display: flex;
flex-wrap: wrap;
gap: 8px;
width: 100%;
padding: 12px;
margin-top: 12px;
background-color: var(--color-neutral-50);
border: 1px solid var(--color-neutral-200);
border-radius: 4px;
& .sound {
padding: 8px 16px;
font-size: var(--font-sm);
font-weight: 500;
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-200);
border-radius: 100px;
}
}
.footer {
display: flex;
column-gap: 8px;
align-items: center;
justify-content: flex-end;
margin-top: 12px;
.button {
padding: 12px 16px;
font-family: var(--font-heading);
font-size: var(--font-sm);
font-weight: 600;
color: var(--color-foreground-subtle);
cursor: pointer;
background-color: var(--color-neutral-200);
border: none;
border-radius: 4px;
outline: none;
transition: 0.2s;
&:hover {
color: var(--color-foreground);
background-color: var(--color-neutral-300);
}
&.primary {
color: var(--color-neutral-200);
background-color: var(--color-neutral-950);
&:hover {
background-color: var(--color-neutral-800);
}
}
}
}

View File

@@ -0,0 +1,107 @@
import { useState, useEffect } from 'react';
import { Modal } from '@/components/modal';
import { useSoundStore } from '@/store';
import { useSnackbar } from '@/contexts/snackbar';
import { cn } from '@/helpers/styles';
import { sounds } from '@/data/sounds';
import styles from './shared.module.css';
export function SharedModal() {
const override = useSoundStore(state => state.override);
const showSnackbar = useSnackbar();
const [isOpen, setIsOpen] = useState(false);
const [sharedSounds, setSharedSounds] = useState<
Array<{
id: string;
label: string;
volume: number;
}>
>([]);
useEffect(() => {
const searchParams = new URLSearchParams(window.location.search);
const share = searchParams.get('share');
if (share) {
try {
const parsed = JSON.parse(decodeURIComponent(share));
const allSounds: Record<string, string> = {};
sounds.categories.forEach(category => {
category.sounds.forEach(sound => {
allSounds[sound.id] = sound.label;
});
});
const _sharedSounds: Array<{
id: string;
label: string;
volume: number;
}> = [];
Object.keys(parsed).forEach(sound => {
if (allSounds[sound]) {
_sharedSounds.push({
id: sound,
label: allSounds[sound],
volume: Number(parsed[sound]),
});
}
});
if (_sharedSounds.length) {
setIsOpen(true);
setSharedSounds(_sharedSounds);
}
} catch (error) {
return;
} finally {
history.pushState({}, '', location.href.split('?')[0]);
}
}
}, []);
const handleOverride = () => {
const newSounds: Record<string, number> = {};
sharedSounds.forEach(sound => {
newSounds[sound.id] = sound.volume;
});
override(newSounds);
setIsOpen(false);
showSnackbar('Done! You can now play the new selection.');
};
return (
<Modal show={isOpen} onClose={() => setIsOpen(false)}>
<h1 className={styles.heading}>New sound mix detected!</h1>
<p className={styles.desc}>
Someone has shared the following mix with you. Would you want to
override your current selection?
</p>
<div className={styles.sounds}>
{sharedSounds.map(sound => (
<div className={styles.sound} key={sound.id}>
{sound.label}
</div>
))}
</div>
<div className={styles.footer}>
<button className={cn(styles.button)} onClick={() => setIsOpen(false)}>
Cancel
</button>
<button
className={cn(styles.button, styles.primary)}
onClick={handleOverride}
>
Override
</button>
</div>
</Modal>
);
}

View File

@@ -1,17 +1,22 @@
.button {
position: fixed;
z-index: 99;
bottom: 20px;
left: 20px;
z-index: 99;
display: flex;
width: 45px;
height: 45px;
align-items: center;
justify-content: center;
border: 1px solid var(--color-neutral-300);
border-radius: 50%;
background-color: var(--color-neutral-100);
width: 45px;
height: 45px;
font-size: var(--font-md);
color: var(--color-foreground);
cursor: pointer;
font-size: var(--font-md);
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-300);
border-radius: 50%;
transition: 0.2s;
&:hover {
background-color: var(--color-neutral-200);
}
}

View File

@@ -28,8 +28,8 @@ const count = soundCount();
& .titleWrapper {
display: flex;
align-items: center;
column-gap: 12px;
align-items: center;
& .title {
margin-bottom: 12px;
@@ -39,8 +39,8 @@ const count = soundCount();
}
& .line {
height: 1px;
flex-grow: 1;
height: 1px;
background: linear-gradient(
90deg,
var(--color-neutral-300),
@@ -51,8 +51,8 @@ const count = soundCount();
}
& .desc {
color: var(--color-foreground-subtle);
line-height: 1.7;
color: var(--color-foreground-subtle);
& span {
color: var(--color-foreground);

View File

@@ -36,8 +36,8 @@ import { Container } from '@/components/container';
& .wrapper {
position: relative;
padding: 0 20px 40px;
border-radius: 0 0 20px 20px;
background: linear-gradient(transparent, var(--color-neutral-100));
border-radius: 0 0 20px 20px;
&::after {
position: absolute;
@@ -45,13 +45,13 @@ import { Container } from '@/components/container';
left: 50%;
width: 70%;
height: 1px;
content: '';
background: linear-gradient(
90deg,
transparent,
var(--color-neutral-300),
transparent
);
content: '';
transform: translateX(-50%);
}
}
@@ -70,15 +70,14 @@ import { Container } from '@/components/container';
& .icon {
display: flex;
width: 45px;
height: 45px;
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%;
background-color: var(--color-neutral-100);
color: #fbbf24;
font-size: var(--font-md);
}
}
@@ -97,23 +96,26 @@ import { Container } from '@/components/container';
& .button {
display: flex;
width: 120px;
height: 40px;
align-items: center;
justify-content: center;
border: none;
border-radius: 100px;
border-top: 2px solid var(--color-neutral-950);
border-bottom: 3px solid var(--color-neutral-600);
width: 120px;
height: 40px;
margin: 24px auto 0;
background-color: var(--color-neutral-700);
color: var(--color-neutral-200);
cursor: pointer;
font-family: var(--font-heading);
font-size: var(--font-sm);
line-height: 0;
outline: none;
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>

View File

@@ -90,8 +90,8 @@ const reasons = [
& .titleWrapper {
display: flex;
align-items: center;
column-gap: 12px;
align-items: center;
& .title {
margin-bottom: 12px;
@@ -101,8 +101,8 @@ const reasons = [
}
& .line {
height: 1px;
flex-grow: 1;
height: 1px;
background: linear-gradient(
90deg,
var(--color-neutral-300),
@@ -114,23 +114,23 @@ const reasons = [
& .reasons {
display: grid;
margin-top: 24px;
column-gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
row-gap: 28px;
column-gap: 20px;
margin-top: 24px;
& .icon {
display: flex;
width: 40px;
height: 40px;
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;
margin-bottom: 12px;
background: linear-gradient(var(--color-neutral-100), transparent);
color: #34d399;
font-size: var(--font-md);
}
& .label {
@@ -140,16 +140,16 @@ const reasons = [
}
& .body {
color: var(--color-foreground-subtle);
line-height: 1.6;
color: var(--color-foreground-subtle);
}
& .link {
display: block;
margin-top: 8px;
color: var(--color-foreground);
font-size: var(--font-sm);
font-weight: 500;
color: var(--color-foreground);
text-decoration: none;
}
}

View File

@@ -1,17 +1,22 @@
.button {
position: fixed;
z-index: 99;
right: 20px;
bottom: 20px;
z-index: 99;
display: flex;
width: 45px;
height: 45px;
align-items: center;
justify-content: center;
border: 1px solid var(--color-neutral-300);
border-radius: 50%;
background-color: var(--color-neutral-100);
width: 45px;
height: 45px;
font-size: var(--font-md);
color: var(--color-foreground);
cursor: pointer;
font-size: var(--font-md);
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-300);
border-radius: 50%;
transition: 0.2s;
&:hover {
background-color: var(--color-neutral-200);
}
}

View File

@@ -1,8 +1,8 @@
.wrapper {
position: fixed;
z-index: 100;
bottom: 20px;
left: 0;
z-index: 100;
width: 100%;
pointer-events: none;
@@ -10,11 +10,11 @@
width: max-content;
max-width: 90%;
padding: 12px 16px;
border: 1px solid var(--color-neutral-300);
border-radius: 4px;
margin: 0 auto;
background-color: var(--color-neutral-200);
font-size: var(--font-sm);
pointer-events: fill;
background-color: var(--color-neutral-200);
border: 1px solid var(--color-neutral-300);
border-radius: 4px;
}
}

View File

@@ -3,20 +3,25 @@
top: 10px;
right: 10px;
display: flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
border: 1px solid var(--color-neutral-200);
border-radius: 50%;
background-color: black;
background-color: var(--color-neutral-100);
width: 30px;
height: 30px;
line-height: 0;
color: var(--color-foreground-subtle);
cursor: pointer;
line-height: 0;
background-color: black;
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-200);
border-radius: 50%;
outline: none;
transition: 0.2s;
&:hover {
color: var(--color-foreground);
}
&.isFavorite {
color: #f43f5e;
color: var(--color-foreground);
}
}

View File

@@ -7,8 +7,8 @@
/* Range Reset */
appearance: none;
background: transparent;
cursor: pointer;
background: transparent;
/* Removes default focus */
&:focus {
@@ -16,27 +16,27 @@
}
&:disabled {
pointer-events: none;
cursor: default;
opacity: 0.5;
pointer-events: none;
}
/***** Chrome, Safari, Opera and Edge Chromium styles *****/
&::-webkit-slider-runnable-track {
height: 0.5rem;
border-radius: 0.5rem;
background-color: #27272a;
border-radius: 0.5rem;
}
&::-webkit-slider-thumb {
width: 14px;
height: 14px;
border: 1px solid #52525b;
border-radius: 50%;
margin-top: -3px;
appearance: none;
background-color: #3f3f46;
border: 1px solid #52525b;
border-radius: 50%;
}
&:not(:disabled):focus::-webkit-slider-thumb {
@@ -49,19 +49,19 @@
&::-moz-range-track {
height: 0.5rem;
border-radius: 0.5rem;
background-color: #27272a;
border-radius: 0.5rem;
}
&::-moz-range-thumb {
width: 14px;
height: 14px;
margin-top: -3px;
background-color: #3f3f46;
border: none;
border: 1px solid #52525b;
border-radius: 0;
border-radius: 50%;
margin-top: -3px;
background-color: #3f3f46;
}
&:not(:disabled):focus::-moz-range-thumb {

View File

@@ -5,10 +5,10 @@
align-items: center;
justify-content: center;
padding: 25px 20px;
text-align: center;
background: linear-gradient(var(--color-neutral-100), transparent);
border: 1px solid var(--color-neutral-200);
border-radius: 8px;
background: linear-gradient(var(--color-neutral-100), transparent);
text-align: center;
transition: 0.2s;
&.hidden {
@@ -21,80 +21,83 @@
left: 0;
width: 100%;
height: 1px;
content: '';
background: linear-gradient(
90deg,
transparent,
var(--color-neutral-400),
transparent
);
content: '';
}
& .icon {
position: relative;
z-index: 2;
display: flex;
width: 40px;
height: 40px;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
font-size: var(--font-base);
color: var(--color-foreground-subtler);
transition: 0.2s;
&::after {
position: absolute;
z-index: -1;
top: 0;
left: 0;
z-index: -1;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: var(--color-neutral-50);
content: '';
background-color: var(--color-neutral-50);
border-radius: 50%;
}
&::before {
position: absolute;
z-index: -2;
top: -1px;
left: -1px;
z-index: -2;
width: calc(100% + 2px);
height: calc(100% + 2px);
border-radius: 50%;
content: '';
background: linear-gradient(
var(--color-neutral-300),
var(--color-neutral-100)
);
content: '';
border-radius: 50%;
}
& .spinner {
animation-duration: 1s;
animation-iteration-count: infinite;
animation-name: spinner;
animation-timing-function: linear;
line-height: 0;
animation-name: spinner;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
}
&:hover .icon {
color: var(--color-foreground-subtle);
}
&.selected {
border-color: transparent;
box-shadow:
0 0 0 2px #34d399,
0 10px 20px #34d39933;
box-shadow: 0 0 0 2px var(--color-neutral-800);
& .icon {
color: #34d399;
color: var(--color-foreground);
}
}
& h3 {
margin-top: 8px;
cursor: default;
font-family: var(--font-heading);
font-size: var(--font-sm);
font-weight: 600;
line-height: 1.6;
cursor: default;
}
}

View File

@@ -1,25 +1,25 @@
.sounds {
display: grid;
margin-top: 20px;
gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-top: 20px;
}
.button {
position: relative;
display: flex;
width: max-content;
height: 35px;
align-items: center;
justify-content: center;
width: max-content;
height: 35px;
padding: 0 15px;
border: 1px solid var(--color-neutral-200);
border-radius: 50px;
margin: 25px auto 0;
background-color: var(--color-neutral-50);
font-size: var(--font-xsm);
color: var(--color-neutral-subtle);
cursor: pointer;
font-size: var(--font-xsm);
background-color: var(--color-neutral-50);
border: 1px solid var(--color-neutral-200);
border-radius: 50px;
&::before {
position: absolute;
@@ -27,13 +27,13 @@
left: 50%;
width: 70%;
height: 1px;
content: '';
background: linear-gradient(
90deg,
transparent,
var(--color-neutral-300),
transparent
);
content: '';
transform: translateX(-50%);
}
@@ -43,8 +43,8 @@
right: 0;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #34d399;
content: '';
background-color: var(--color-neutral-950);
border-radius: 50%;
}
}

View File

@@ -1,8 +1,8 @@
.tooltip {
width: max-content;
padding: 6px 12px;
font-size: var(--font-xsm);
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-200);
border-radius: 100px;
background-color: var(--color-neutral-100);
font-size: var(--font-xsm);
}

View File

@@ -2,6 +2,8 @@ import { nature } from './sounds/nature';
import { rain } from './sounds/rain';
import { animals } from './sounds/animals';
import { urban } from './sounds/urban';
import { places } from './sounds/places';
import { transport } from './sounds/transport';
import { things } from './sounds/things';
import { noise } from './sounds/noise';
@@ -10,5 +12,5 @@ import type { Categories } from './types';
export const sounds: {
categories: Categories;
} = {
categories: [nature, rain, animals, urban, things, noise],
categories: [nature, rain, animals, urban, places, transport, things, noise],
};

View File

@@ -1,6 +1,6 @@
import { GiCricket, GiSeagull, GiWolfHead, GiOwl } from 'react-icons/gi/index';
import { FaDog, FaFrog } from 'react-icons/fa/index';
import { PiBirdFill } from 'react-icons/pi/index';
import { FaDog, FaFrog, FaHorseHead, FaCat } from 'react-icons/fa/index';
import { PiBirdFill, PiDogBold } from 'react-icons/pi/index';
import type { Category } from '../types';
@@ -44,6 +44,24 @@ export const animals: Category = {
label: 'Frog',
src: '/sounds/animals/frog.mp3',
},
{
icon: <PiDogBold />,
id: 'dog-barking',
label: 'Dog Barking',
src: '/sounds/animals/dog-barking.mp3',
},
{
icon: <FaHorseHead />,
id: 'horse-galopp',
label: 'Horse Galopp',
src: '/sounds/animals/horse-galopp.mp3',
},
{
icon: <FaCat />,
id: 'cat-purring',
label: 'Cat Purring',
src: '/sounds/animals/cat-purring.mp3',
},
],
title: 'Animals',
};

View File

@@ -1,8 +1,7 @@
import { GiWaterfall } from 'react-icons/gi/index';
import { BsFire } from 'react-icons/bs/index';
import { BsFire, BsFillDropletFill } from 'react-icons/bs/index';
import { BiSolidTree, BiWater } from 'react-icons/bi/index';
import { FaWater, FaWind, FaLeaf, FaRegSnowflake } from 'react-icons/fa/index';
import { TbScubaMask, TbSailboat } from 'react-icons/tb/index';
import type { Category } from '../types';
@@ -52,12 +51,6 @@ export const nature: Category = {
label: 'Waterfall',
src: '/sounds/nature/waterfall.mp3',
},
{
icon: <TbScubaMask />,
id: 'underwater',
label: 'Underwater',
src: '/sounds/nature/underwater.mp3',
},
{
icon: <FaRegSnowflake />,
id: 'walk-in-snow',
@@ -71,10 +64,10 @@ export const nature: Category = {
src: '/sounds/nature/walk-on-leaves.mp3',
},
{
icon: <TbSailboat />,
id: 'rowing-boat',
label: 'Rowing Boat',
src: '/sounds/nature/rowing-boat.mp3',
icon: <BsFillDropletFill />,
id: 'droplets',
label: 'Droplets',
src: '/sounds/nature/droplets.mp3',
},
],
title: 'Nature',

View File

@@ -0,0 +1,54 @@
import { BiSolidCoffeeAlt, BiSolidPlaneAlt } from 'react-icons/bi/index';
import { FaChurch } from 'react-icons/fa/index';
import { TbScubaMask } from 'react-icons/tb/index';
import {
MdTempleBuddhist,
MdConstruction,
MdLocationPin,
} from 'react-icons/md/index';
import type { Category } from '../types';
export const places: Category = {
icon: <MdLocationPin />,
id: 'places',
sounds: [
{
icon: <BiSolidCoffeeAlt />,
id: 'cafe',
label: 'Cafe',
src: '/sounds/places/cafe.mp3',
},
{
icon: <BiSolidPlaneAlt />,
id: 'airport',
label: 'Airport',
src: '/sounds/places/airport.mp3',
},
{
icon: <FaChurch />,
id: 'church',
label: 'Church',
src: '/sounds/places/church.mp3',
},
{
icon: <MdTempleBuddhist />,
id: 'temple',
label: 'Temple',
src: '/sounds/places/temple.mp3',
},
{
icon: <MdConstruction />,
id: 'construction-site',
label: 'Construction Site',
src: '/sounds/places/construction-site.mp3',
},
{
icon: <TbScubaMask />,
id: 'underwater',
label: 'Underwater',
src: '/sounds/places/underwater.mp3',
},
],
title: 'Places',
};

View File

@@ -1,6 +1,6 @@
import { GiWindchimes } from 'react-icons/gi/index';
import { BsFillKeyboardFill } from 'react-icons/bs/index';
import { FaKeyboard, FaClock } from 'react-icons/fa/index';
import { FaKeyboard, FaClock, FaFan } from 'react-icons/fa/index';
import { MdSmartToy } from 'react-icons/md/index';
import { TbBowlFilled } from 'react-icons/tb/index';
import { RiFilePaper2Fill } from 'react-icons/ri/index';
@@ -47,6 +47,12 @@ export const things: Category = {
label: 'Singing Bowl',
src: '/sounds/things/singing-bowl.mp3',
},
{
icon: <FaFan />,
id: 'ceiling-fan',
label: 'Ceiling Fan',
src: '/sounds/things/ceiling-fan.mp3',
},
],
title: 'Things',
};

View File

@@ -0,0 +1,50 @@
import { BiSolidTrain, BiSolidPlaneAlt } from 'react-icons/bi/index';
import { FaCarSide } from 'react-icons/fa/index';
import { GiSubmarine, GiSailboat } from 'react-icons/gi/index';
import { TbSailboat } from 'react-icons/tb/index';
import type { Category } from '../types';
export const transport: Category = {
icon: <FaCarSide />,
id: 'transport',
sounds: [
{
icon: <BiSolidTrain />,
id: 'train',
label: 'Train',
src: '/sounds/transport/train.mp3',
},
{
icon: <BiSolidTrain />,
id: 'inside-a-train',
label: 'Inside a Train',
src: '/sounds/transport/inside-a-train.mp3',
},
{
icon: <BiSolidPlaneAlt />,
id: 'airplane',
label: 'Airplane',
src: '/sounds/transport/airplane.mp3',
},
{
icon: <GiSubmarine />,
id: 'submarine',
label: 'Submarine',
src: '/sounds/transport/submarine.mp3',
},
{
icon: <GiSailboat />,
id: 'sailboat',
label: 'Sailboat',
src: '/sounds/transport/sailboat.mp3',
},
{
icon: <TbSailboat />,
id: 'rowing-boat',
label: 'Rowing Boat',
src: '/sounds/transport/rowing-boat.mp3',
},
],
title: 'Transport',
};

View File

@@ -1,10 +1,7 @@
import {
BiSolidCoffeeAlt,
BiSolidTrain,
BiSolidPlaneAlt,
} from 'react-icons/bi/index';
import { BiSolidTraffic } from 'react-icons/bi/index';
import { FaCity, FaRoad } from 'react-icons/fa/index';
import { PiRoadHorizonFill, PiSirenBold } from 'react-icons/pi/index';
import { BsSoundwave, BsPeopleFill } from 'react-icons/bs/index';
import type { Category } from '../types';
@@ -12,12 +9,6 @@ export const urban: Category = {
icon: <FaCity />,
id: 'urban',
sounds: [
{
icon: <BiSolidCoffeeAlt />,
id: 'cafe',
label: 'Cafe',
src: '/sounds/urban/cafe.mp3',
},
{
icon: <PiRoadHorizonFill />,
id: 'highway',
@@ -37,28 +28,22 @@ export const urban: Category = {
src: '/sounds/urban/ambulance-siren.mp3',
},
{
icon: <BiSolidTrain />,
id: 'train',
label: 'Train',
src: '/sounds/urban/train.mp3',
icon: <BsSoundwave />,
id: 'busy-street',
label: 'Busy Street',
src: '/sounds/urban/busy-street.mp3',
},
{
icon: <BiSolidTrain />,
id: 'inside-a-train',
label: 'Inside a Train',
src: '/sounds/urban/inside-a-train.mp3',
icon: <BsPeopleFill />,
id: 'crowd',
label: 'Crowd',
src: '/sounds/urban/crowd.mp3',
},
{
icon: <BiSolidPlaneAlt />,
id: 'airport',
label: 'Airport',
src: '/sounds/urban/airport.mp3',
},
{
icon: <BiSolidPlaneAlt />,
id: 'airplane',
label: 'Airplane',
src: '/sounds/urban/airplane.mp3',
icon: <BiSolidTraffic />,
id: 'traffic',
label: 'Traffic',
src: '/sounds/urban/traffic.mp3',
},
],
title: 'Urban',

19
src/hooks/use-copy.ts Normal file
View File

@@ -0,0 +1,19 @@
import { useState, useCallback } from 'react';
export function useCopy(timeout = 1500) {
const [copying, setCopying] = useState(false);
const copy = useCallback(
(content: string) => {
if (copying) return;
navigator.clipboard.writeText(content);
setCopying(true);
setTimeout(() => setCopying(false), timeout);
},
[copying, timeout],
);
return { copy, copying };
}

View File

@@ -5,6 +5,7 @@ import type { SoundState } from './sound.state';
import { pickMany, random } from '@/helpers/random';
export interface SoundActions {
override: (sounds: Record<string, number>) => void;
pause: () => void;
play: () => void;
restoreHistory: () => void;
@@ -24,6 +25,19 @@ export const createActions: StateCreator<
SoundActions
> = (set, get) => {
return {
override(newSounds) {
get().unselectAll();
const sounds = get().sounds;
Object.keys(newSounds).forEach(sound => {
sounds[sound].isSelected = true;
sounds[sound].volume = newSounds[sound];
});
set({ sounds: { ...sounds } });
},
pause() {
set({ isPlaying: false });
},

View File

@@ -12,13 +12,13 @@ html {
}
body {
background-color: var(--color-neutral-50);
color: var(--color-foreground);
font-family: var(--font-body);
font-size: var(--font-base);
color: var(--color-foreground);
background-color: var(--color-neutral-50);
}
::selection {
background-color: var(--color-neutral-300);
color: var(--color-foreground);
background-color: var(--color-neutral-300);
}

View File

@@ -1,35 +1,44 @@
/* fraunces-600 - latin */
@font-face {
font-display: swap;
font-family: Fraunces;
font-style: normal;
font-weight: 600;
src: url('/fonts/fraunces-v31-latin-600.woff2') format('woff2');
font-display: swap;
}
/* inter-regular - latin */
@font-face {
font-display: swap;
font-family: Inter;
font-style: normal;
font-weight: 400;
src: url('/fonts/inter-v13-latin-regular.woff2') format('woff2');
font-display: swap;
}
/* inter-500 - latin */
@font-face {
font-display: swap;
font-family: Inter;
font-style: normal;
font-weight: 500;
src: url('../fonts/inter-v13-latin-500.woff2') format('woff2');
font-display: swap;
}
/* inter-tight-600 - latin */
@font-face {
font-display: swap;
font-family: 'Inter Tight';
font-style: normal;
font-weight: 600;
src: url('/fonts/inter-tight-v7-latin-600.woff2') format('woff2');
font-display: swap;
}
/* inter-tight-700 - latin */
@font-face {
font-family: 'Inter Tight';
font-style: normal;
font-weight: 700;
src: url('/fonts/inter-tight-v7-latin-700.woff2') format('woff2');
font-display: swap;
}

View File

@@ -12,7 +12,7 @@
--color-neutral-950: #fafafa;
/* Foreground */
--color-foreground: var(--color-neutral-900);
--color-foreground: var(--color-neutral-950);
--color-foreground-subtle: var(--color-neutral-600);
--color-foreground-subtler: var(--color-neutral-500);
}