feat: remove all tools

This commit is contained in:
MAZE 2024-08-30 16:33:20 +03:30
parent 1768ba1548
commit b32d8b2803
34 changed files with 0 additions and 1122 deletions

View file

@ -18,10 +18,6 @@ const paragraphs = [
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 offers more than ambient sounds with its suite of productivity tools to keep you organized and focused. Use the built-in pomodoro timer for structured work intervals, jot down ideas in the notepad, and track tasks with the to-do list (coming soon). These tools integrate seamlessly with the ambient soundscapes, creating a personalized environment that fosters 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',

View file

@ -1,13 +0,0 @@
import { IoMdFlower } from 'react-icons/io/index';
import { Item } from '../item';
export function BreathingExercise() {
return (
<Item
href="/tools/breathing-exercises"
icon={<IoMdFlower />}
label="Breathing Exercise"
/>
);
}

View file

@ -1,13 +0,0 @@
import { MdOutlineTimer } from 'react-icons/md/index';
import { Item } from '../item';
export function CountdownTimer() {
return (
<Item
href="https://timesy.app"
icon={<MdOutlineTimer />}
label="Countdown Timer"
/>
);
}

View file

@ -1,11 +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';
export { Shortcuts as ShortcutsItem } from './shortcuts';
export { SleepTimer as SleepTimerItem } from './sleep-timer';
export { CountdownTimer as CountdownTimerItem } from './countdown-timer';
export { BreathingExercise as BreathingExerciseItem } from './breathing-exercise';

View file

@ -1,18 +0,0 @@
import { MdNotes } from 'react-icons/md/index';
import { Item } from '../item';
import { useNoteStore } from '@/stores/note';
export function Notepad() {
const note = useNoteStore(state => state.note);
return (
<Item
active={!!note.length}
href="/tools/notepad"
icon={<MdNotes />}
label="Notepad"
/>
);
}

View file

@ -1,9 +0,0 @@
import { MdOutlineAvTimer } from 'react-icons/md/index';
import { Item } from '../item';
export function Pomodoro() {
return (
<Item href="/tools/pomodoro" icon={<MdOutlineAvTimer />} label="Pomodoro" />
);
}

View file

@ -8,14 +8,10 @@ import {
ShuffleItem,
ShareItem,
DonateItem,
NotepadItem,
SourceItem,
PomodoroItem,
CountdownTimerItem,
PresetsItem,
ShortcutsItem,
SleepTimerItem,
BreathingExerciseItem,
} from './items';
import { Divider } from './divider';
import { ShareLinkModal } from '@/components/modals/share-link';
@ -104,12 +100,6 @@ export function Menu() {
<ShuffleItem />
<SleepTimerItem open={() => open('sleepTimer')} />
<Divider />
<BreathingExerciseItem />
<PomodoroItem />
<NotepadItem />
<CountdownTimerItem />
<Divider />
<ShortcutsItem open={() => open('shortcuts')} />

View file

@ -21,18 +21,6 @@ export function ShortcutsModal({ onClose, show }: ShortcutsModalProps) {
keys: ['Shift', 'S'],
label: 'Share Sounds',
},
{
keys: ['Shift', 'N'],
label: 'Notepad',
},
{
keys: ['Shift', 'P'],
label: 'Pomodoro Timer',
},
{
keys: ['Shift', 'B'],
label: 'Breathing Exercise',
},
{
keys: ['Shift', 'T'],
label: 'Sleep Timer',

View file

@ -1,33 +0,0 @@
---
import { Container } from '../container';
interface Props {
text: string;
}
const { text } = Astro.props;
---
<section class="about">
<Container tight>
<h2 class="title">About this tool</h2>
<p class="text">{text}</p>
</Container>
</section>
<style>
.about {
margin-top: 75px;
& .title {
margin-bottom: 8px;
font-family: var(--font-heading);
font-size: var(--font-md);
font-weight: 600;
}
& .text {
color: var(--color-foreground-subtle);
}
}
</style>

View file

@ -1,47 +0,0 @@
.exercise {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 75px 0;
margin-top: 12px;
background-color: var(--color-neutral-50);
border: 1px solid var(--color-neutral-200);
border-radius: 8px;
& .phase {
font-family: var(--font-display);
font-size: var(--font-lg);
font-weight: 600;
}
& .circle {
position: absolute;
top: 50%;
left: 50%;
z-index: -1;
height: 55%;
aspect-ratio: 1 / 1;
background-image: radial-gradient(
var(--color-neutral-50),
var(--color-neutral-100)
);
border: 1px solid var(--color-neutral-200);
border-radius: 50%;
transform: translate(-50%, -50%);
}
}
.selectBox {
width: 100%;
min-width: 0;
height: 45px;
padding: 0 12px;
margin-top: 8px;
font-size: var(--font-sm);
color: var(--color-foreground);
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-200);
border-radius: 8px;
}

View file

@ -1,124 +0,0 @@
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { Container } from '@/components/container';
import styles from './breathing.module.css';
type Exercise = 'Box Breathing' | 'Resonant Breathing' | '4-7-8 Breathing';
type Phase = 'inhale' | 'exhale' | 'holdInhale' | 'holdExhale';
export function Breathing() {
const [selectedExercise, setSelectedExercise] =
useState<Exercise>('4-7-8 Breathing');
const getAnimationPhases = (
exercise: Exercise,
): Array<'inhale' | 'holdInhale' | 'exhale' | 'holdExhale'> => {
switch (exercise) {
case 'Box Breathing':
return ['inhale', 'holdInhale', 'exhale', 'holdExhale'];
case 'Resonant Breathing':
return ['inhale', 'exhale'];
case '4-7-8 Breathing':
return ['inhale', 'holdInhale', 'exhale'];
default:
return ['inhale', 'holdInhale', 'exhale', 'holdExhale'];
}
};
const getAnimationDurations = (exercise: Exercise) => {
switch (exercise) {
case 'Box Breathing':
return { exhale: 4, holdExhale: 4, holdInhale: 4, inhale: 4 };
case 'Resonant Breathing':
return { exhale: 5, inhale: 5 };
case '4-7-8 Breathing':
return { exhale: 8, holdInhale: 7, inhale: 4 };
default:
return { exhale: 4, holdExhale: 4, holdInhale: 4, inhale: 4 };
}
};
const getLabel = (phase: Phase) => {
switch (phase) {
case 'inhale':
return 'Inhale';
case 'exhale':
return 'Exhale';
default:
return 'Hold';
}
};
const [phase, setPhase] = useState<Phase>('inhale');
const [durations, setDurations] = useState(
getAnimationDurations(selectedExercise),
);
const animationVariants = {
exhale: {
transform: 'translate(-50%, -50%) scale(1)',
transition: { duration: durations.exhale },
},
holdExhale: {
transform: 'translate(-50%, -50%) scale(1)',
transition: { duration: durations.holdExhale || 4 },
},
holdInhale: {
transform: 'translate(-50%, -50%) scale(1.5)',
transition: { duration: durations.holdInhale || 4 },
},
inhale: {
transform: 'translate(-50%, -50%) scale(1.5)',
transition: { duration: durations.inhale },
},
};
useEffect(() => {
setDurations(getAnimationDurations(selectedExercise));
}, [selectedExercise]);
useEffect(() => {
const phases = getAnimationPhases(selectedExercise);
let phaseIndex = 0;
setPhase(phases[phaseIndex]);
const interval = setInterval(
() => {
phaseIndex = (phaseIndex + 1) % phases.length;
setPhase(phases[phaseIndex]);
},
(durations[phases[phaseIndex]] || 4) * 1000,
);
return () => clearInterval(interval);
}, [selectedExercise, durations]);
return (
<Container>
<div className={styles.exercise}>
<motion.div
animate={phase}
className={styles.circle}
key={selectedExercise}
variants={animationVariants}
/>
<p className={styles.phase}>{getLabel(phase)}</p>
</div>
<select
className={styles.selectBox}
value={selectedExercise}
onChange={e => setSelectedExercise(e.target.value as Exercise)}
>
<option value="Box Breathing">Box Breathing</option>
<option value="Resonant Breathing">Resonant Breathing</option>
<option value="4-7-8 Breathing">4-7-8 Breathing</option>
</select>
</Container>
);
}

View file

@ -1 +0,0 @@
export { Breathing } from './breathing';

View file

@ -1,34 +0,0 @@
.button {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
font-size: var(--font-sm);
color: var(--color-foreground);
cursor: pointer;
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-200);
border-radius: 4px;
outline: none;
transition: 0.2s;
&:focus-visible {
outline: 2px solid var(--color-neutral-400);
outline-offset: 2px;
}
&:hover,
&:focus-visible {
background-color: var(--color-neutral-200);
}
&.smallIcon {
font-size: var(--font-xsm);
}
&:disabled {
cursor: not-allowed;
opacity: 0.4;
}
}

View file

@ -1,33 +0,0 @@
import { Tooltip } from '@/components/tooltip';
import { cn } from '@/helpers/styles';
import styles from './button.module.css';
interface ButtonProps {
disabled?: boolean;
icon: React.ReactElement;
onClick: () => void;
smallIcon?: boolean;
tooltip: string;
}
export function Button({
disabled = false,
icon,
onClick,
smallIcon,
tooltip,
}: ButtonProps) {
return (
<Tooltip content={tooltip} placement="bottom" showDelay={0}>
<button
className={cn(styles.button, smallIcon && styles.smallIcon)}
disabled={disabled}
onClick={onClick}
>
{icon}
</button>
</Tooltip>
);
}

View file

@ -1 +0,0 @@
export { Button } from './button';

View file

@ -1,55 +0,0 @@
---
import { Container } from '../container';
interface Props {
desc: string;
title: string;
}
const { desc, title } = Astro.props;
---
<section class="hero">
<Container>
<img alt="Moodist Logo" class="logo" src="/logo.svg" />
<h1>{title}</h1>
<p class="desc">{desc}</p>
<p class="moodist">Part of <a href="/">Moodist</a></p>
</Container>
</section>
<style>
.hero {
padding: 75px 0;
text-align: center;
& .logo {
display: block;
width: 45px;
margin: 0 auto 16px;
}
h1 {
font-family: var(--font-display);
font-size: var(--font-lg);
font-weight: 600;
}
.desc {
margin-top: 4px;
color: var(--color-foreground-subtle);
}
.moodist {
margin-top: 16px;
font-size: var(--font-sm);
color: var(--color-foreground-subtle);
& a {
font-weight: 500;
color: var(--color-foreground);
text-decoration: none;
}
}
}
</style>

View file

@ -1,45 +0,0 @@
.button {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
font-size: var(--font-sm);
color: var(--color-foreground);
cursor: pointer;
background-color: var(--color-neutral-100);
border: 1px solid var(--color-neutral-200);
border-radius: 4px;
outline: none;
transition: 0.2s;
transition-property: border-color, color, background-color;
&:focus-visible {
outline: 2px solid var(--color-neutral-400);
outline-offset: 2px;
}
&.critical {
color: #f43f5e;
border-color: #f43f5e;
&:hover {
background-color: rgb(244 63 94 / 10%);
}
}
&.recommended {
font-size: var(--font-xsm);
color: #22c55e;
border-color: #22c55e;
&:hover {
background-color: rgb(34 197 94 / 10%);
}
}
&:hover,
&:focus-visible {
background-color: var(--color-neutral-200);
}
}

View file

@ -1,36 +0,0 @@
import { Tooltip } from '@/components/tooltip';
import { cn } from '@/helpers/styles';
import styles from './button.module.css';
interface ButtonProps {
critical?: boolean;
icon: React.ReactElement;
onClick: () => void;
recommended?: boolean;
tooltip: string;
}
export function Button({
critical,
icon,
onClick,
recommended,
tooltip,
}: ButtonProps) {
return (
<Tooltip content={tooltip} placement="bottom" showDelay={0}>
<button
className={cn(
styles.button,
critical && styles.critical,
recommended && styles.recommended,
)}
onClick={onClick}
>
{icon}
</button>
</Tooltip>
);
}

View file

@ -1 +0,0 @@
export { Button } from './button';

View file

@ -1 +0,0 @@
export { Notepad } from './notepad';

View file

@ -1,44 +0,0 @@
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
& .label {
font-size: var(--font-sm);
font-weight: 500;
color: var(--color-foreground-subtle);
}
& .buttons {
display: flex;
column-gap: 4px;
align-items: center;
}
}
.textarea {
width: 100%;
height: 350px;
padding: 12px;
line-height: 1.6;
color: var(--color-foreground-subtle);
resize: none;
background-color: var(--color-neutral-50);
border: 1px solid var(--color-neutral-200);
border-radius: 4px;
outline: none;
scroll-padding-bottom: 12px;
&:focus-visible {
outline: 2px solid var(--color-neutral-400);
outline-offset: 2px;
}
}
.counter {
margin-top: 8px;
font-size: var(--font-xsm);
color: var(--color-foreground-subtle);
text-align: center;
}

View file

@ -1,66 +0,0 @@
import { BiTrash } from 'react-icons/bi/index';
import { LuCopy, LuDownload } from 'react-icons/lu/index';
import { FaCheck } from 'react-icons/fa6/index';
import { FaUndo } from 'react-icons/fa/index';
import { Button } from './button';
import { useNoteStore } from '@/stores/note';
import { useCopy } from '@/hooks/use-copy';
import { download } from '@/helpers/download';
import styles from './notepad.module.css';
import { Container } from '@/components/container';
export function Notepad() {
const note = useNoteStore(state => state.note);
const history = useNoteStore(state => state.history);
const write = useNoteStore(state => state.write);
const words = useNoteStore(state => state.words());
const characters = useNoteStore(state => state.characters());
const clear = useNoteStore(state => state.clear);
const restore = useNoteStore(state => state.restore);
const { copy, copying } = useCopy();
return (
<Container>
<header className={styles.header}>
<h2 className={styles.label}>Your Note</h2>
<div className={styles.buttons}>
<Button
icon={copying ? <FaCheck /> : <LuCopy />}
tooltip="Copy Note"
onClick={() => copy(note)}
/>
<Button
icon={<LuDownload />}
tooltip="Download Note"
onClick={() => download('Moodit Note.txt', note)}
/>
<Button
critical={!history}
icon={history ? <FaUndo /> : <BiTrash />}
recommended={!!history}
tooltip={history ? 'Restore Note' : 'Clear Note'}
onClick={() => (history ? restore() : clear())}
/>
</div>
</header>
<textarea
className={styles.textarea}
dir="auto"
placeholder="What is on your mind?"
spellCheck={false}
value={note}
onChange={e => write(e.target.value)}
/>
<p className={styles.counter}>
{characters} character{characters !== 1 && 's'} {words} word
{words !== 1 && 's'}
</p>
</Container>
);
}

View file

@ -1 +0,0 @@
export { Pomodoro } from './pomodoro';

View file

@ -1,36 +0,0 @@
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
& .title {
font-size: var(--font-sm);
font-weight: 500;
color: var(--color-foreground-subtle);
}
& .buttons {
display: flex;
column-gap: 4px;
align-items: center;
}
}
.control {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
& .completed {
font-size: var(--font-xsm);
color: var(--color-foreground-subtle);
}
& .buttons {
display: flex;
column-gap: 4px;
align-items: center;
}
}

View file

@ -1,168 +0,0 @@
import { useState, useEffect, useRef, useMemo } from 'react';
import { FaUndo, FaPlay, FaPause } from 'react-icons/fa/index';
import { IoMdSettings } from 'react-icons/io/index';
import { Button } from '../generics/button';
import { Timer } from '@/components/timer';
import { Container } from '@/components/container';
import { Tabs } from './tabs';
import { Setting } from './setting';
import { useLocalStorage } from '@/hooks/use-local-storage';
import { useSoundEffect } from '@/hooks/use-sound-effect';
import { usePomodoroStore } from '@/stores/pomodoro';
import { useCloseListener } from '@/hooks/use-close-listener';
import styles from './pomodoro.module.css';
export function Pomodoro() {
const [showSetting, setShowSetting] = useState(false);
const [selectedTab, setSelectedTab] = useState('pomodoro');
const running = usePomodoroStore(state => state.running);
const setRunning = usePomodoroStore(state => state.setRunning);
const [timer, setTimer] = useState(0);
const interval = useRef<ReturnType<typeof setInterval> | null>(null);
const alarm = useSoundEffect('/sounds/alarm.mp3');
const defaultTimes = useMemo(
() => ({
long: 15 * 60,
pomodoro: 25 * 60,
short: 5 * 60,
}),
[],
);
const [times, setTimes] = useLocalStorage<Record<string, number>>(
'moodist-pomodoro-setting',
defaultTimes,
);
const [completions, setCompletions] = useState<Record<string, number>>({
long: 0,
pomodoro: 0,
short: 0,
});
const tabs = useMemo(
() => [
{ id: 'pomodoro', label: 'Pomodoro' },
{ id: 'short', label: 'Break' },
{ id: 'long', label: 'Long Break' },
],
[],
);
useCloseListener(() => setShowSetting(false));
useEffect(() => {
if (running) {
if (interval.current) clearInterval(interval.current);
interval.current = setInterval(() => {
setTimer(prev => prev - 1);
}, 1000);
} else {
if (interval.current) clearInterval(interval.current);
}
}, [running]);
useEffect(() => {
if (timer <= 0 && running) {
if (interval.current) clearInterval(interval.current);
alarm.play();
setRunning(false);
setCompletions(prev => ({
...prev,
[selectedTab]: prev[selectedTab] + 1,
}));
}
}, [timer, selectedTab, running, setRunning, alarm]);
useEffect(() => {
const time = times[selectedTab] || 10;
if (interval.current) clearInterval(interval.current);
setRunning(false);
setTimer(time);
}, [selectedTab, times, setRunning]);
const toggleRunning = () => {
if (running) setRunning(false);
else if (timer <= 0) {
const time = times[selectedTab] || 10;
setTimer(time);
setRunning(true);
} else setRunning(true);
};
const restart = () => {
if (interval.current) clearInterval(interval.current);
const time = times[selectedTab] || 10;
setRunning(false);
setTimer(time);
};
return (
<Container>
<header className={styles.header}>
<h2 className={styles.title}>Pomodoro Timer</h2>
<div className={styles.button}>
<Button
icon={<IoMdSettings />}
tooltip="Change Times"
onClick={() => {
setShowSetting(true);
}}
/>
</div>
</header>
<Tabs selectedTab={selectedTab} tabs={tabs} onSelect={setSelectedTab} />
<Timer timer={timer} />
<div className={styles.control}>
<p className={styles.completed}>
{completions[selectedTab] || 0} completed
</p>
<div className={styles.buttons}>
<Button
icon={<FaUndo />}
smallIcon
tooltip="Restart"
onClick={restart}
/>
<Button
icon={running ? <FaPause /> : <FaPlay />}
smallIcon
tooltip={running ? 'Pause' : 'Start'}
onClick={toggleRunning}
/>
</div>
</div>
<Setting
show={showSetting}
times={times}
onChange={times => {
setShowSetting(false);
setTimes(times);
}}
onClose={() => {
setShowSetting(false);
}}
/>
</Container>
);
}

View file

@ -1 +0,0 @@
export { Setting } from './setting';

View file

@ -1,76 +0,0 @@
.title {
margin-bottom: 16px;
font-family: var(--font-heading);
font-size: var(--font-md);
font-weight: 600;
}
& .form {
display: flex;
flex-direction: column;
& .field {
display: flex;
flex-direction: column;
row-gap: 8px;
margin-bottom: 16px;
& .label {
font-size: var(--font-sm);
color: var(--color-foreground);
& span {
color: var(--color-foreground-subtle);
}
}
& .input {
display: block;
height: 40px;
padding: 0 8px;
color: var(--color-foreground);
background-color: var(--color-neutral-50);
border: 1px solid var(--color-neutral-200);
border-radius: 4px;
outline: none;
&:focus-visible {
outline: 2px solid var(--color-neutral-400);
outline-offset: 2px;
}
}
}
& .buttons {
display: flex;
column-gap: 8px;
align-items: center;
justify-content: flex-end;
& button {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
padding: 0 16px;
font-size: var(--font-sm);
font-weight: 500;
color: var(--color-foreground);
cursor: pointer;
background-color: var(--color-neutral-200);
border: none;
border-radius: 4px;
outline: none;
&:focus-visible {
outline: 2px solid var(--color-neutral-400);
outline-offset: 2px;
}
&.primary {
color: var(--color-neutral-100);
background-color: var(--color-neutral-950);
}
}
}
}

View file

@ -1,110 +0,0 @@
import { useEffect, useState } from 'react';
import { Modal } from '@/components/modal';
import styles from './setting.module.css';
interface SettingProps {
onChange: (newTimes: Record<string, number>) => void;
onClose: () => void;
show: boolean;
times: Record<string, number>;
}
export function Setting({ onChange, onClose, show, times }: SettingProps) {
const [values, setValues] = useState<Record<string, number | string>>(times);
useEffect(() => {
if (show) setValues(times);
}, [times, show]);
const handleChange = (id: string) => (value: number | string) => {
setValues(prev => ({
...prev,
[id]: typeof value === 'number' ? value * 60 : '',
}));
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const newValues: Record<string, number> = {};
Object.keys(values).forEach(name => {
newValues[name] =
typeof values[name] === 'number' ? values[name] : times[name];
});
onChange(newValues);
};
const handleCancel = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault();
onClose();
};
return (
<Modal lockBody={false} show={show} onClose={onClose}>
<h2 className={styles.title}>Change Times</h2>
<form className={styles.form} onSubmit={handleSubmit}>
<Field
id="pomodoro"
label="Pomodoro"
value={values.pomodoro}
onChange={handleChange('pomodoro')}
/>
<Field
id="short"
label="Short Break"
value={values.short}
onChange={handleChange('short')}
/>
<Field
id="long"
label="Long Break"
value={values.long}
onChange={handleChange('long')}
/>
<div className={styles.buttons}>
<button type="button" onClick={handleCancel}>
Cancel
</button>
<button className={styles.primary} type="submit">
Save
</button>
</div>
</form>
</Modal>
);
}
interface FieldProps {
id: string;
label: string;
onChange: (value: number | string) => void;
value: number | string;
}
function Field({ id, label, onChange, value }: FieldProps) {
return (
<div className={styles.field}>
<label className={styles.label} htmlFor={id}>
{label} <span>(minutes)</span>
</label>
<input
className={styles.input}
max={120}
min={1}
required
type="number"
value={typeof value === 'number' ? value / 60 : ''}
onChange={e => {
onChange(e.target.value === '' ? '' : Number(e.target.value));
}}
/>
</div>
);
}

View file

@ -1 +0,0 @@
export { Tabs } from './tabs';

View file

@ -1,43 +0,0 @@
.tabs {
display: flex;
column-gap: 4px;
align-items: center;
padding: 4px;
margin: 8px 0;
background-color: var(--color-neutral-50);
border: 1px solid var(--color-neutral-200);
border-radius: 8px;
& .tab {
display: flex;
flex-grow: 1;
align-items: center;
justify-content: center;
height: 45px;
font-size: var(--font-sm);
color: var(--color-foreground-subtle);
cursor: pointer;
background-color: transparent;
border: 1px solid transparent;
border-radius: 4px;
outline: none;
transition: 0.2s;
&:focus-visible {
outline: 2px solid var(--color-neutral-400);
outline-offset: 2px;
}
&.selected {
color: var(--color-foreground);
background-color: var(--color-neutral-200);
border-color: var(--color-neutral-300);
}
&:not(.selected):hover,
&:not(.selected):focus-visible {
color: var(--color-foreground);
background-color: var(--color-neutral-100);
}
}
}

View file

@ -1,25 +0,0 @@
import { cn } from '@/helpers/styles';
import styles from './tabs.module.css';
interface TabsProps {
onSelect: (id: string) => void;
selectedTab: string;
tabs: Array<{ id: string; label: string }>;
}
export function Tabs({ onSelect, selectedTab, tabs }: TabsProps) {
return (
<div className={styles.tabs}>
{tabs.map(tab => (
<button
className={cn(styles.tab, selectedTab === tab.id && styles.selected)}
key={tab.id}
onClick={() => onSelect(tab.id)}
>
{tab.label}
</button>
))}
</div>
);
}

View file

@ -1,19 +0,0 @@
---
import Layout from '@/layouts/layout.astro';
import Donate from '@/components/donate.astro';
import Hero from '@/components/tools/hero.astro';
import Footer from '@/components/footer.astro';
import About from '@/components/tools/about.astro';
import { Breathing } from '@/components/tools/breathing';
---
<Layout title="Breathing Exercises Tool — Moodist">
<Donate />
<Hero desc="Simple breathing exercises." title="Breathing Exercises" />
<Breathing client:load />
<About
text="Experience calm and focus with our simple breathing exercise tool. Designed to guide your breath, it helps reduce stress, enhance relaxation, and improve mindfulness in just a few minutes a day."
/>
<Footer />
</Layout>

View file

@ -1,19 +0,0 @@
---
import Layout from '@/layouts/layout.astro';
import Donate from '@/components/donate.astro';
import Hero from '@/components/tools/hero.astro';
import Footer from '@/components/footer.astro';
import About from '@/components/tools/about.astro';
import { Notepad as NotepadComponent } from '@/components/tools/notepad';
---
<Layout title="Online Notepad — Moodist">
<Donate />
<Hero desc="Distraction-free online notepad." title="Notepad" />
<NotepadComponent client:load />
<About
text="Capture your thoughts instantly with our simple online notepad. Lightweight and easy to use, it lets you jot down notes, ideas, and to-do lists anytime, anywhere, without distractions."
/>
<Footer />
</Layout>

View file

@ -1,19 +0,0 @@
---
import Layout from '@/layouts/layout.astro';
import Donate from '@/components/donate.astro';
import Hero from '@/components/tools/hero.astro';
import Footer from '@/components/footer.astro';
import About from '@/components/tools/about.astro';
import { Pomodoro as PomodoroTimer } from '@/components/tools/pomodoro';
---
<Layout title="Pomodoro Timer — Moodist">
<Donate />
<Hero desc="Super simple pomodoro timer." title="Pomodoro Timer" />
<PomodoroTimer client:load />
<About
text="Boost your productivity with our simple Pomodoro timer. Designed to help you stay focused, it breaks your work into manageable intervals with regular breaks, making it easier to stay on track and avoid burnout."
/>
<Footer />
</Layout>