829 lines
27 KiB
Svelte
829 lines
27 KiB
Svelte
<script lang="ts">
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { timer, currentView } from "../stores/timer";
|
|
import { config, autoSave, loadConfig, resetConfig } from "../stores/config";
|
|
import ToggleSwitch from "./ToggleSwitch.svelte";
|
|
import Stepper from "./Stepper.svelte";
|
|
import ColorPicker from "./ColorPicker.svelte";
|
|
import FontSelector from "./FontSelector.svelte";
|
|
import TimeSpinner from "./TimeSpinner.svelte";
|
|
import { fadeIn, inView, pressable, dragScroll } from "../utils/animate";
|
|
import { playSound } from "../utils/sounds";
|
|
import type { TimeRange } from "../stores/config";
|
|
|
|
const soundPresets = ["bell", "chime", "soft", "digital", "harp", "bowl", "rain", "whistle"] as const;
|
|
const daysOfWeek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] as const;
|
|
|
|
function goBack() {
|
|
invoke("set_view", { view: "dashboard" });
|
|
currentView.set("dashboard");
|
|
}
|
|
|
|
function markChanged() {
|
|
autoSave();
|
|
}
|
|
|
|
// Working hours functions
|
|
function toggleDayEnabled(dayIndex: number) {
|
|
$config.working_hours_schedule[dayIndex].enabled = !$config.working_hours_schedule[dayIndex].enabled;
|
|
$config.working_hours_schedule = $config.working_hours_schedule;
|
|
markChanged();
|
|
}
|
|
|
|
function addTimeRange(dayIndex: number) {
|
|
$config.working_hours_schedule[dayIndex].ranges = [
|
|
...$config.working_hours_schedule[dayIndex].ranges,
|
|
{ start: "09:00", end: "18:00" }
|
|
];
|
|
$config.working_hours_schedule = $config.working_hours_schedule;
|
|
markChanged();
|
|
}
|
|
|
|
function removeTimeRange(dayIndex: number, rangeIndex: number) {
|
|
if ($config.working_hours_schedule[dayIndex].ranges.length > 1) {
|
|
$config.working_hours_schedule[dayIndex].ranges = $config.working_hours_schedule[dayIndex].ranges.filter((_, i) => i !== rangeIndex);
|
|
$config.working_hours_schedule = $config.working_hours_schedule;
|
|
markChanged();
|
|
}
|
|
}
|
|
|
|
function cloneTimeRange(dayIndex: number, rangeIndex: number) {
|
|
const range = $config.working_hours_schedule[dayIndex].ranges[rangeIndex];
|
|
$config.working_hours_schedule[dayIndex].ranges = [
|
|
...$config.working_hours_schedule[dayIndex].ranges,
|
|
{ ...range }
|
|
];
|
|
$config.working_hours_schedule = $config.working_hours_schedule;
|
|
markChanged();
|
|
}
|
|
|
|
function updateTimeRange(dayIndex: number, rangeIndex: number, field: "start" | "end", value: string) {
|
|
$config.working_hours_schedule[dayIndex].ranges[rangeIndex][field] = value;
|
|
$config.working_hours_schedule = $config.working_hours_schedule;
|
|
markChanged();
|
|
}
|
|
|
|
// Reset button two-click confirmation
|
|
let resetConfirming = $state(false);
|
|
let resetTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
function handleReset() {
|
|
if (!resetConfirming) {
|
|
resetConfirming = true;
|
|
// Auto-cancel after 3 seconds
|
|
resetTimeout = setTimeout(() => {
|
|
resetConfirming = false;
|
|
}, 3000);
|
|
} else {
|
|
resetConfirming = false;
|
|
if (resetTimeout) clearTimeout(resetTimeout);
|
|
resetConfig();
|
|
autoSave();
|
|
}
|
|
}
|
|
|
|
// Reload config when entering settings
|
|
$effect(() => {
|
|
loadConfig();
|
|
});
|
|
</script>
|
|
|
|
<div class="flex h-full flex-col">
|
|
<!-- Header -->
|
|
<div
|
|
data-tauri-drag-region
|
|
class="flex items-center px-5 pt-5 pb-4"
|
|
use:fadeIn={{ duration: 0.4, y: 8 }}
|
|
>
|
|
<button
|
|
aria-label="Back to dashboard"
|
|
use:pressable
|
|
class="mr-3 flex h-8 w-8 items-center justify-center rounded-full
|
|
text-[#8a8a8a] transition-colors hover:text-white"
|
|
onclick={goBack}
|
|
>
|
|
<svg
|
|
aria-hidden="true"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<polyline points="15 18 9 12 15 6" />
|
|
</svg>
|
|
</button>
|
|
<h1
|
|
data-tauri-drag-region
|
|
tabindex="-1"
|
|
class="flex-1 text-lg font-medium text-white"
|
|
>
|
|
Settings
|
|
</h1>
|
|
</div>
|
|
|
|
<!-- Scrollable content with drag scroll -->
|
|
<div
|
|
class="settings-scroll-container flex-1 overflow-y-auto px-5 pb-6"
|
|
use:dragScroll
|
|
>
|
|
<div class="space-y-3">
|
|
<!-- 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"
|
|
>
|
|
Timer
|
|
</h3>
|
|
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<div class="text-[13px] text-white">Break frequency</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
Every {$config.break_frequency} min
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.break_frequency}
|
|
label="Break frequency"
|
|
min={5}
|
|
max={120}
|
|
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">Break duration</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
{$config.break_duration} min
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.break_duration}
|
|
label="Break duration"
|
|
min={1}
|
|
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">Auto-start</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">Start timer on launch</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.auto_start}
|
|
label="Auto-start"
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Break Screen -->
|
|
<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"
|
|
>
|
|
Break Screen
|
|
</h3>
|
|
|
|
<div class="flex flex-col gap-1.5">
|
|
<label class="text-[13px] text-white" for="break-title">
|
|
Break title
|
|
</label>
|
|
<input
|
|
id="break-title"
|
|
type="text"
|
|
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]"
|
|
placeholder="Enter break title..."
|
|
bind:value={$config.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="break-message">
|
|
Break message
|
|
</label>
|
|
<input
|
|
id="break-message"
|
|
type="text"
|
|
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]"
|
|
placeholder="Enter break message..."
|
|
bind:value={$config.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">Fullscreen break</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
{$config.fullscreen_mode ? "Fills entire screen" : "Centered modal"}
|
|
</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.fullscreen_mode}
|
|
label="Fullscreen break"
|
|
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">Activity suggestions</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
Exercise ideas during breaks
|
|
</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.show_break_activities}
|
|
label="Activity suggestions"
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
</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"
|
|
>
|
|
Behavior
|
|
</h3>
|
|
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<div class="text-[13px] text-white">Strict mode</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
Disable skip and snooze
|
|
</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.strict_mode}
|
|
label="Strict mode"
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
|
|
{#if !$config.strict_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">Allow end early</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">After 50% of break</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.allow_end_early}
|
|
label="Allow end early"
|
|
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">Snooze duration</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
{$config.snooze_duration} min
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.snooze_duration}
|
|
label="Snooze duration"
|
|
min={1}
|
|
max={30}
|
|
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">Snooze limit</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
{$config.snooze_limit === 0
|
|
? "Unlimited"
|
|
: `${$config.snooze_limit} per break`}
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.snooze_limit}
|
|
label="Snooze limit"
|
|
min={0}
|
|
max={5}
|
|
formatValue={(v) => (v === 0 ? "\u221E" : String(v))}
|
|
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">Immediate breaks</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
Skip pre-break warning
|
|
</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.immediately_start_breaks}
|
|
label="Immediate breaks"
|
|
onchange={markChanged}
|
|
/>
|
|
</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 }}>
|
|
<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-[#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}
|
|
{/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
|
|
</h3>
|
|
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<div class="text-[13px] text-white">Auto-pause when idle</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">Pause timer when away</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.idle_detection_enabled}
|
|
label="Auto-pause when idle"
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
|
|
{#if $config.idle_detection_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">Idle timeout</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
{$config.idle_timeout}s of inactivity
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.idle_timeout}
|
|
label="Idle timeout"
|
|
min={30}
|
|
max={600}
|
|
step={30}
|
|
formatValue={(v) => `${v}s`}
|
|
onchange={markChanged}
|
|
/>
|
|
</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="flex items-center">
|
|
<div class="flex-1">
|
|
<div class="text-[13px] text-white">Enable smart breaks</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">Auto-reset timer when you step away</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.smart_breaks_enabled}
|
|
label="Enable smart breaks"
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
|
|
{#if $config.smart_breaks_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">Minimum away time</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
{$config.smart_break_threshold >= 60
|
|
? `${Math.floor($config.smart_break_threshold / 60)} min`
|
|
: `${$config.smart_break_threshold}s`} to count as break
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.smart_break_threshold}
|
|
label="Minimum away time"
|
|
min={120}
|
|
max={900}
|
|
step={60}
|
|
formatValue={(v) => `${Math.floor(v / 60)}m`}
|
|
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">Count in statistics</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">Track natural breaks in stats</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.smart_break_count_stats}
|
|
label="Count in statistics"
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
{/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
|
|
</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>
|
|
<ToggleSwitch
|
|
bind:checked={$config.notification_enabled}
|
|
label="Pre-break alert"
|
|
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
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.notification_before_break}
|
|
label="Alert timing"
|
|
min={0}
|
|
max={300}
|
|
step={10}
|
|
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
|
|
</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-[#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>
|
|
</div>
|
|
{/if}
|
|
</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"
|
|
>
|
|
Appearance
|
|
</h3>
|
|
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<div class="text-[13px] text-white">UI zoom</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
{$config.ui_zoom}%
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.ui_zoom}
|
|
label="UI zoom"
|
|
min={50}
|
|
max={200}
|
|
step={5}
|
|
formatValue={(v) => `${v}%`}
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
|
|
<div class="my-4 h-px bg-[#161616]"></div>
|
|
|
|
<ColorPicker
|
|
label="Accent color"
|
|
bind:value={$config.accent_color}
|
|
onchange={markChanged}
|
|
/>
|
|
|
|
<div class="my-4 h-px bg-[#161616]"></div>
|
|
|
|
<ColorPicker
|
|
label="Break screen color"
|
|
bind:value={$config.break_color}
|
|
presets={[
|
|
"#7c6aef", "#9b5de5", "#4361ee", "#4895ef",
|
|
"#2ec4b6", "#06d6a0", "#3fb950", "#80ed99",
|
|
"#f72585", "#ff006e", "#e63946", "#ff4d00",
|
|
"#fca311", "#ffbe0b", "#ffffff", "#888888",
|
|
]}
|
|
onchange={markChanged}
|
|
/>
|
|
|
|
<div class="my-4 h-px bg-[#161616]"></div>
|
|
|
|
<FontSelector
|
|
bind:value={$config.countdown_font}
|
|
onchange={markChanged}
|
|
/>
|
|
|
|
<div class="my-4 h-px bg-[#161616]"></div>
|
|
|
|
<div class="flex items-center">
|
|
<div class="flex-1">
|
|
<div class="text-[13px] text-white">Animated background</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
Gradient blobs with film grain
|
|
</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.background_blobs_enabled}
|
|
label="Animated background"
|
|
onchange={markChanged}
|
|
/>
|
|
</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"
|
|
>
|
|
Mini Mode
|
|
</h3>
|
|
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<div class="text-[13px] text-white">Click-through</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
Mini timer ignores clicks until you hover over it
|
|
</div>
|
|
</div>
|
|
<ToggleSwitch
|
|
bind:checked={$config.mini_click_through}
|
|
label="Click-through"
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
|
|
{#if $config.mini_click_through}
|
|
<div class="mt-4 flex items-center justify-between">
|
|
<div>
|
|
<div class="text-[13px] text-white">Hover delay</div>
|
|
<div class="text-[11px] text-[#8a8a8a]">
|
|
Seconds to hover before it becomes draggable
|
|
</div>
|
|
</div>
|
|
<Stepper
|
|
bind:value={$config.mini_hover_threshold}
|
|
label="Hover delay"
|
|
min={1}
|
|
max={10}
|
|
step={0.5}
|
|
formatValue={(v) => `${v}s`}
|
|
onchange={markChanged}
|
|
/>
|
|
</div>
|
|
{/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"
|
|
>
|
|
Keyboard Shortcuts
|
|
</h3>
|
|
|
|
<div class="space-y-3">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-[13px] text-white">Pause / Resume</span>
|
|
<kbd class="rounded-lg bg-[#161616] px-2.5 py-1 text-[11px] text-[#8a8a8a]">Ctrl+Shift+P</kbd>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-[13px] text-white">Start break now</span>
|
|
<kbd class="rounded-lg bg-[#161616] px-2.5 py-1 text-[11px] text-[#8a8a8a]">Ctrl+Shift+B</kbd>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-[13px] text-white">Show / Hide window</span>
|
|
<kbd class="rounded-lg bg-[#161616] px-2.5 py-1 text-[11px] text-[#8a8a8a]">Ctrl+Shift+S</kbd>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Reset -->
|
|
<div class="pt-2 pb-6" use:inView={{ delay: 0.39 }}>
|
|
<button
|
|
use:pressable
|
|
class="w-full rounded-full border py-3 text-[12px]
|
|
tracking-wider uppercase
|
|
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'}"
|
|
onclick={handleReset}
|
|
>
|
|
{resetConfirming ? "Tap again to confirm reset" : "Reset to defaults"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|