Add pomodoro, microbreaks, breathing guide, screen dimming, presentation mode, goals, multi-monitor, and activity manager
This commit is contained in:
@@ -7,10 +7,36 @@
|
||||
import ColorPicker from "./ColorPicker.svelte";
|
||||
import FontSelector from "./FontSelector.svelte";
|
||||
import TimeSpinner from "./TimeSpinner.svelte";
|
||||
import ActivityManager from "./ActivityManager.svelte";
|
||||
import { fadeIn, inView, pressable, dragScroll } from "../utils/animate";
|
||||
import { playSound } from "../utils/sounds";
|
||||
import type { TimeRange } from "../stores/config";
|
||||
|
||||
const breathingPatternMeta = [
|
||||
{ id: "box", label: "Box", desc: "4s in \u00b7 4s hold \u00b7 4s out \u00b7 4s hold" },
|
||||
{ id: "relaxing", label: "Relaxing", desc: "4s in \u00b7 7s hold \u00b7 8s out" },
|
||||
{ id: "energizing", label: "Energizing", desc: "6s in \u00b7 2s hold \u00b7 6s out \u00b7 2s hold" },
|
||||
{ id: "calm", label: "Calm", desc: "4s in \u00b7 4s hold \u00b7 6s out" },
|
||||
{ id: "deep", label: "Deep", desc: "5s in \u00b7 5s out" },
|
||||
] as const;
|
||||
|
||||
// F8: Auto-start on login
|
||||
let autoStartEnabled = $state(false);
|
||||
async function loadAutoStartStatus() {
|
||||
try {
|
||||
autoStartEnabled = await invoke<boolean>("get_auto_start_status");
|
||||
} catch {}
|
||||
}
|
||||
async function toggleAutoStart() {
|
||||
try {
|
||||
await invoke("set_auto_start", { enabled: !autoStartEnabled });
|
||||
autoStartEnabled = !autoStartEnabled;
|
||||
} catch (e) {
|
||||
console.error("Failed to set auto-start:", e);
|
||||
}
|
||||
}
|
||||
$effect(() => { loadAutoStartStatus(); });
|
||||
|
||||
const soundPresets = ["bell", "chime", "soft", "digital", "harp", "bowl", "rain", "whistle"] as const;
|
||||
const daysOfWeek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] as const;
|
||||
|
||||
@@ -131,11 +157,10 @@
|
||||
use:dragScroll
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<!-- Timer -->
|
||||
|
||||
<!-- 1. Timer -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Timer
|
||||
</h3>
|
||||
|
||||
@@ -188,11 +213,137 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Break Screen -->
|
||||
<!-- 2. Pomodoro Mode -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.03 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Pomodoro Mode
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Enable Pomodoro</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Short breaks then a long break</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.pomodoro_enabled} label="Pomodoro mode" onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
{#if $config.pomodoro_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Short breaks before long</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">{$config.pomodoro_short_breaks} short + 1 long</div>
|
||||
</div>
|
||||
<Stepper bind:value={$config.pomodoro_short_breaks} label="Short breaks" min={1} max={10} onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Long break duration</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">{$config.pomodoro_long_break_duration} min</div>
|
||||
</div>
|
||||
<Stepper bind:value={$config.pomodoro_long_break_duration} label="Long break duration" min={5} max={60} onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<label class="text-[13px] text-white" for="pomo-title">Long break title</label>
|
||||
<input id="pomo-title" type="text" maxlength={100}
|
||||
class="rounded-xl border border-[#161616] bg-black px-3.5 py-2.5 text-[13px] text-white outline-none transition-colors placeholder:text-[#2a2a2a] focus:border-[#333]"
|
||||
bind:value={$config.pomodoro_long_break_title} oninput={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<label class="text-[13px] text-white" for="pomo-msg">Long break message</label>
|
||||
<input id="pomo-msg" type="text" maxlength={500}
|
||||
class="rounded-xl border border-[#161616] bg-black px-3.5 py-2.5 text-[13px] text-white outline-none transition-colors placeholder:text-[#2a2a2a] focus:border-[#333]"
|
||||
bind:value={$config.pomodoro_long_break_message} oninput={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Reset on skip</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Reset cycle when skipping a break</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.pomodoro_reset_on_skip} label="Reset on skip" onchange={markChanged} />
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- 3. Microbreaks -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.06 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Microbreaks
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">20-20-20 eye breaks</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Quick eye rest reminders</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.microbreak_enabled}
|
||||
label="Microbreaks"
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $config.microbreak_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Frequency</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Every {$config.microbreak_frequency} min</div>
|
||||
</div>
|
||||
<Stepper bind:value={$config.microbreak_frequency} label="Microbreak frequency" min={5} max={60} onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Duration</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">{$config.microbreak_duration} seconds</div>
|
||||
</div>
|
||||
<Stepper bind:value={$config.microbreak_duration} label="Microbreak duration" min={10} max={60} step={5} onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Sound</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Play sound on eye break</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.microbreak_sound_enabled} label="Microbreak sound" onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Show activity</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Activity suggestion during eye break</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.microbreak_show_activity} label="Microbreak activity" onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Pause during breaks</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">No eye breaks during main breaks</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.microbreak_pause_during_break} label="Pause during breaks" onchange={markChanged} />
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- 4. Break Screen (stripped down) -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.09 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Break Screen
|
||||
</h3>
|
||||
|
||||
@@ -261,13 +412,90 @@
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $config.fullscreen_mode}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Block all monitors</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Show overlay on all screens during breaks</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.multi_monitor_break}
|
||||
label="Block all monitors"
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Behavior -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.12 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
<!-- 5. Break Activities (own card, conditional) -->
|
||||
{#if $config.show_break_activities}
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.12 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Break Activities
|
||||
</h3>
|
||||
<ActivityManager />
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- 6. Breathing Guide (own card) -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.15 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Breathing Guide
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Guided breathing</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Visual breathing guide during breaks</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.breathing_guide_enabled}
|
||||
label="Guided breathing"
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $config.breathing_guide_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div>
|
||||
<div class="mb-3 text-[13px] text-white">Breathing pattern</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each breathingPatternMeta as bp}
|
||||
<button
|
||||
use:pressable
|
||||
class="flex items-center gap-3 rounded-xl px-3.5 py-2.5 text-left
|
||||
transition-all duration-200
|
||||
{$config.breathing_pattern === bp.id
|
||||
? 'bg-[#1a1a1a] border border-[#333]'
|
||||
: 'bg-[#0a0a0a] border border-[#161616] hover:border-[#333]'}"
|
||||
onclick={() => {
|
||||
$config.breathing_pattern = bp.id;
|
||||
markChanged();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="w-3 h-3 rounded-full border-2 flex-shrink-0 transition-colors duration-200"
|
||||
style="border-color: {$config.breathing_pattern === bp.id ? $config.accent_color : '#333'};
|
||||
background: {$config.breathing_pattern === bp.id ? $config.accent_color : 'transparent'};"
|
||||
></div>
|
||||
<span class="text-[12px] font-medium {$config.breathing_pattern === bp.id ? 'text-white' : 'text-[#8a8a8a]'}">
|
||||
{bp.label}
|
||||
</span>
|
||||
<span class="ml-auto text-[11px] text-[#8a8a8a] opacity-60 tabular-nums">
|
||||
{bp.desc}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- 7. Behavior -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.18 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Behavior
|
||||
</h3>
|
||||
|
||||
@@ -357,114 +585,152 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Working Hours -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.18 }}>
|
||||
<!-- 8. Alerts (MERGED: Notifications + Pre-Break Nudge) -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.21 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Alerts
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Working hours</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">
|
||||
Only show breaks during your configured work schedule
|
||||
</div>
|
||||
<div class="text-[13px] text-white">Pre-break alert</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Warn before breaks</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.working_hours_enabled}
|
||||
label="Working hours"
|
||||
bind:checked={$config.notification_enabled}
|
||||
label="Pre-break alert"
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $config.working_hours_enabled}
|
||||
{#if $config.notification_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
{#each $config.working_hours_schedule as daySchedule, dayIndex}
|
||||
{@const dayName = daysOfWeek[dayIndex]}
|
||||
<div class="mb-4">
|
||||
<!-- Day header with toggle -->
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.working_hours_schedule[dayIndex].enabled}
|
||||
label={dayName}
|
||||
onchange={markChanged}
|
||||
/>
|
||||
<span class="text-[13px] text-white w-20">{dayName}</span>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Alert timing</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">
|
||||
{$config.notification_before_break}s before
|
||||
</div>
|
||||
|
||||
{#if daySchedule.enabled}
|
||||
<!-- Time ranges for this day -->
|
||||
<div class="space-y-2">
|
||||
{#each daySchedule.ranges as range, rangeIndex}
|
||||
<div class="flex items-center gap-2">
|
||||
<TimeSpinner
|
||||
value={range.start}
|
||||
countdownFont={$config.countdown_font}
|
||||
onchange={(v) => updateTimeRange(dayIndex, rangeIndex, "start", v)}
|
||||
/>
|
||||
<span class="text-[#555] text-[13px]">to</span>
|
||||
<TimeSpinner
|
||||
value={range.end}
|
||||
countdownFont={$config.countdown_font}
|
||||
onchange={(v) => updateTimeRange(dayIndex, rangeIndex, "end", v)}
|
||||
/>
|
||||
|
||||
<!-- Add range button -->
|
||||
{#if rangeIndex === daySchedule.ranges.length - 1}
|
||||
<button
|
||||
use:pressable
|
||||
class="ml-2 w-8 h-8 flex items-center justify-center rounded-full text-[#444] hover:text-white hover:bg-[#1a1a1a] transition-colors"
|
||||
onclick={() => addTimeRange(dayIndex)}
|
||||
aria-label="Add time range"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 5v14M5 12h14"/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Clone button -->
|
||||
<button
|
||||
use:pressable
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full text-[#444] hover:text-white hover:bg-[#1a1a1a] transition-colors"
|
||||
onclick={() => cloneTimeRange(dayIndex, rangeIndex)}
|
||||
aria-label="Clone time range"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Delete button (never show for first range) -->
|
||||
{#if rangeIndex > 0}
|
||||
<button
|
||||
use:pressable
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full text-[#444] hover:text-[#f85149] hover:bg-[#f8514915] transition-colors"
|
||||
onclick={() => removeTimeRange(dayIndex, rangeIndex)}
|
||||
aria-label="Remove time range"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 6L6 18M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if dayIndex < 6}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
{/if}
|
||||
{/each}
|
||||
<Stepper
|
||||
bind:value={$config.notification_before_break}
|
||||
label="Alert timing"
|
||||
min={0}
|
||||
max={300}
|
||||
step={10}
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Screen dimming</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Gradually dim screen before breaks</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.screen_dim_enabled} label="Screen dimming" onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
{#if $config.screen_dim_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Start dimming</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">{$config.screen_dim_seconds}s before break</div>
|
||||
</div>
|
||||
<Stepper bind:value={$config.screen_dim_seconds} label="Dim start" min={3} max={60} onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Max dimming</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">{Math.round($config.screen_dim_max_opacity * 100)}%</div>
|
||||
</div>
|
||||
<Stepper
|
||||
bind:value={$config.screen_dim_max_opacity}
|
||||
label="Max dim opacity"
|
||||
min={0.1}
|
||||
max={0.7}
|
||||
step={0.05}
|
||||
formatValue={(v) => `${Math.round(v * 100)}%`}
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Idle Detection -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.21 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
Idle Detection
|
||||
<!-- 9. Sound -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.24 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Sound
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Sound effects</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Play sounds on break events</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.sound_enabled}
|
||||
label="Sound effects"
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $config.sound_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Volume</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">{$config.sound_volume}%</div>
|
||||
</div>
|
||||
<Stepper
|
||||
bind:value={$config.sound_volume}
|
||||
label="Volume"
|
||||
min={0}
|
||||
max={100}
|
||||
step={10}
|
||||
formatValue={(v) => `${v}%`}
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
<div>
|
||||
<div class="mb-3 text-[13px] text-white">Sound preset</div>
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
{#each soundPresets as preset}
|
||||
<button
|
||||
use:pressable
|
||||
class="rounded-xl py-2.5 text-[11px] tracking-wider uppercase
|
||||
transition-all duration-200
|
||||
{$config.sound_preset === preset
|
||||
? 'bg-[#1a1a1a] text-white border border-[#333]'
|
||||
: 'bg-[#0a0a0a] text-[#8a8a8a] border border-[#161616] hover:border-[#333] hover:text-white'}"
|
||||
onclick={() => {
|
||||
$config.sound_preset = preset;
|
||||
markChanged();
|
||||
playSound(preset, $config.sound_volume);
|
||||
}}
|
||||
>
|
||||
{preset}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- 10. Idle & Smart Breaks (MERGED) -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.27 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Idle & Smart Breaks
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
@@ -500,19 +766,12 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Smart Breaks -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.24 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
Smart Breaks
|
||||
</h3>
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Enable smart breaks</div>
|
||||
<div class="text-[13px] text-white">Smart breaks</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Auto-reset timer when you step away</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
@@ -561,119 +820,90 @@
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Notifications -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.27 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
Notifications
|
||||
<!-- 11. Presentation Mode -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.30 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Presentation Mode
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Pre-break alert</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Warn before breaks</div>
|
||||
<div class="text-[13px] text-white">Auto-detect fullscreen</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Defer breaks during fullscreen apps</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.notification_enabled}
|
||||
label="Pre-break alert"
|
||||
onchange={markChanged}
|
||||
/>
|
||||
<ToggleSwitch bind:checked={$config.presentation_mode_enabled} label="Presentation mode" onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
{#if $config.notification_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Alert timing</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">
|
||||
{$config.notification_before_break}s before
|
||||
{#if $config.presentation_mode_enabled}
|
||||
{#if $config.microbreak_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Defer microbreaks</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Also pause eye breaks</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.presentation_mode_defer_microbreaks} label="Defer microbreaks" onchange={markChanged} />
|
||||
</div>
|
||||
<Stepper
|
||||
bind:value={$config.notification_before_break}
|
||||
label="Alert timing"
|
||||
min={0}
|
||||
max={300}
|
||||
step={10}
|
||||
onchange={markChanged}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Notification</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Show toast when break is deferred</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.presentation_mode_notification} label="Deferral notification" onchange={markChanged} />
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Sound -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.27 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
Sound
|
||||
<!-- 12. Goals & Streaks -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.33 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Goals & Streaks
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Sound effects</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Play sounds on break events</div>
|
||||
<div class="text-[13px] text-white">Daily goal</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Track daily break target</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.sound_enabled}
|
||||
label="Sound effects"
|
||||
onchange={markChanged}
|
||||
/>
|
||||
<ToggleSwitch bind:checked={$config.daily_goal_enabled} label="Daily goal" onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
{#if $config.sound_enabled}
|
||||
{#if $config.daily_goal_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Volume</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">{$config.sound_volume}%</div>
|
||||
</div>
|
||||
<Stepper
|
||||
bind:value={$config.sound_volume}
|
||||
label="Volume"
|
||||
min={0}
|
||||
max={100}
|
||||
step={10}
|
||||
formatValue={(v) => `${v}%`}
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
<div>
|
||||
<div class="mb-3 text-[13px] text-white">Sound preset</div>
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
{#each soundPresets as preset}
|
||||
<button
|
||||
use:pressable
|
||||
class="rounded-xl py-2.5 text-[11px] tracking-wider uppercase
|
||||
transition-all duration-200
|
||||
{$config.sound_preset === preset
|
||||
? 'bg-[#1a1a1a] text-white border border-[#333]'
|
||||
: 'bg-[#0a0a0a] text-[#555] border border-[#161616] hover:border-[#333] hover:text-[#999]'}"
|
||||
onclick={() => {
|
||||
$config.sound_preset = preset;
|
||||
markChanged();
|
||||
playSound(preset, $config.sound_volume);
|
||||
}}
|
||||
>
|
||||
{preset}
|
||||
</button>
|
||||
{/each}
|
||||
<div class="text-[13px] text-white">Target breaks</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">{$config.daily_goal_breaks} per day</div>
|
||||
</div>
|
||||
<Stepper bind:value={$config.daily_goal_breaks} label="Daily goal breaks" min={1} max={30} onchange={markChanged} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Celebrations</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Confetti on milestones and goals</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.milestone_celebrations} label="Celebrations" onchange={markChanged} />
|
||||
</div>
|
||||
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Streak notifications</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Toast on streak milestones</div>
|
||||
</div>
|
||||
<ToggleSwitch bind:checked={$config.streak_notifications} label="Streak notifications" onchange={markChanged} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Appearance -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.3 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
<!-- 13. Appearance -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.36 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Appearance
|
||||
</h3>
|
||||
|
||||
@@ -741,11 +971,111 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Mini Mode -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.33 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
<!-- 14. Working Hours -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.39 }}>
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Working hours</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">
|
||||
Only show breaks during your configured work schedule
|
||||
</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.working_hours_enabled}
|
||||
label="Working hours"
|
||||
onchange={markChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $config.working_hours_enabled}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
|
||||
{#each $config.working_hours_schedule as daySchedule, dayIndex}
|
||||
{@const dayName = daysOfWeek[dayIndex]}
|
||||
<div class="mb-4">
|
||||
<!-- Day header with toggle -->
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<ToggleSwitch
|
||||
bind:checked={$config.working_hours_schedule[dayIndex].enabled}
|
||||
label={dayName}
|
||||
onchange={markChanged}
|
||||
/>
|
||||
<span class="text-[13px] text-white w-20">{dayName}</span>
|
||||
</div>
|
||||
|
||||
{#if daySchedule.enabled}
|
||||
<!-- Time ranges for this day -->
|
||||
<div class="space-y-2">
|
||||
{#each daySchedule.ranges as range, rangeIndex}
|
||||
<div class="flex items-center gap-2">
|
||||
<TimeSpinner
|
||||
value={range.start}
|
||||
countdownFont={$config.countdown_font}
|
||||
onchange={(v) => updateTimeRange(dayIndex, rangeIndex, "start", v)}
|
||||
/>
|
||||
<span class="text-[#8a8a8a] text-[13px]">to</span>
|
||||
<TimeSpinner
|
||||
value={range.end}
|
||||
countdownFont={$config.countdown_font}
|
||||
onchange={(v) => updateTimeRange(dayIndex, rangeIndex, "end", v)}
|
||||
/>
|
||||
|
||||
<!-- Add range button -->
|
||||
{#if rangeIndex === daySchedule.ranges.length - 1}
|
||||
<button
|
||||
use:pressable
|
||||
class="ml-2 w-8 h-8 flex items-center justify-center rounded-full text-[#8a8a8a] hover:text-white hover:bg-[#1a1a1a] transition-colors"
|
||||
onclick={() => addTimeRange(dayIndex)}
|
||||
aria-label="Add time range"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 5v14M5 12h14"/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Clone button -->
|
||||
<button
|
||||
use:pressable
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full text-[#8a8a8a] hover:text-white hover:bg-[#1a1a1a] transition-colors"
|
||||
onclick={() => cloneTimeRange(dayIndex, rangeIndex)}
|
||||
aria-label="Clone time range"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Delete button (never show for first range) -->
|
||||
{#if rangeIndex > 0}
|
||||
<button
|
||||
use:pressable
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full text-[#8a8a8a] hover:text-[#f85149] hover:bg-[#f8514915] transition-colors"
|
||||
onclick={() => removeTimeRange(dayIndex, rangeIndex)}
|
||||
aria-label="Remove time range"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 6L6 18M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if dayIndex < 6}
|
||||
<div class="my-4 h-px bg-[#161616]"></div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- 15. Mini Mode -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.42 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Mini Mode
|
||||
</h3>
|
||||
|
||||
@@ -784,11 +1114,28 @@
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Shortcuts -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.36 }}>
|
||||
<h3
|
||||
class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase"
|
||||
>
|
||||
<!-- 16. General -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.45 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
General
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="text-[13px] text-white">Start on Windows login</div>
|
||||
<div class="text-[11px] text-[#8a8a8a]">Launch automatically at startup</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={autoStartEnabled}
|
||||
label="Start on login"
|
||||
onchange={toggleAutoStart}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 17. Keyboard Shortcuts -->
|
||||
<section class="rounded-2xl p-5 backdrop-blur-xl" style="background: rgba(17,17,17,0.7);" use:inView={{ delay: 0.48 }}>
|
||||
<h3 class="mb-4 text-[11px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">
|
||||
Keyboard Shortcuts
|
||||
</h3>
|
||||
|
||||
@@ -808,8 +1155,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Reset -->
|
||||
<div class="pt-2 pb-6" use:inView={{ delay: 0.39 }}>
|
||||
<!-- 18. Reset -->
|
||||
<div class="pt-2 pb-6" use:inView={{ delay: 0.51 }}>
|
||||
<button
|
||||
use:pressable
|
||||
class="w-full rounded-full border py-3 text-[12px]
|
||||
@@ -817,7 +1164,7 @@
|
||||
transition-all duration-200
|
||||
{resetConfirming
|
||||
? 'border-[#f85149] text-[#f85149] hover:bg-[#f85149] hover:text-white'
|
||||
: 'border-[#1a1a1a] text-[#444] hover:border-[#333] hover:text-white'}"
|
||||
: 'border-[#1a1a1a] text-[#8a8a8a] hover:border-[#333] hover:text-white'}"
|
||||
onclick={handleReset}
|
||||
>
|
||||
{resetConfirming ? "Tap again to confirm reset" : "Reset to defaults"}
|
||||
|
||||
Reference in New Issue
Block a user