Initial commit - Core Cooldown v0.1.0

Portable Windows break timer to prevent RSI and eye strain.
Tauri v2 + Svelte 5 + Tailwind CSS v4. No installer, no telemetry,
no data leaves the machine. CC0 public domain.
This commit is contained in:
Your Name
2026-02-07 01:12:32 +02:00
commit 0cbd8abad4
48 changed files with 15133 additions and 0 deletions

118
src/App.svelte Normal file
View File

@@ -0,0 +1,118 @@
<script lang="ts">
import { onMount } from "svelte";
import { fly, scale, fade } from "svelte/transition";
import { cubicOut } from "svelte/easing";
import { invoke } from "@tauri-apps/api/core";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { initTimerStore, currentView } from "./lib/stores/timer";
import { loadConfig, config } from "./lib/stores/config";
import Titlebar from "./lib/components/Titlebar.svelte";
import Dashboard from "./lib/components/Dashboard.svelte";
import BreakScreen from "./lib/components/BreakScreen.svelte";
import Settings from "./lib/components/Settings.svelte";
import StatsView from "./lib/components/StatsView.svelte";
import BackgroundBlobs from "./lib/components/BackgroundBlobs.svelte";
const appWindow = getCurrentWebviewWindow();
onMount(async () => {
await loadConfig();
await initTimerStore();
// Save window position on move/resize (debounced)
let posTimer: ReturnType<typeof setTimeout>;
const savePos = () => {
clearTimeout(posTimer);
posTimer = setTimeout(async () => {
try {
const pos = await appWindow.outerPosition();
const size = await appWindow.outerSize();
await invoke("save_window_position", {
label: "main", x: pos.x, y: pos.y,
width: size.width, height: size.height,
});
} catch {}
}, 500);
};
appWindow.onMoved(savePos);
appWindow.onResized(savePos);
});
// Apply UI zoom from config
const zoomScale = $derived($config.ui_zoom / 100);
// Track previous view for directional transitions
let previousView = $state<string>("dashboard");
$effect(() => {
const view = $currentView;
// Store previous for determining transition direction
return () => {
previousView = view;
};
});
// Transition parameters
const DURATION = 700;
const easing = cubicOut;
// When fullscreen_mode is OFF, the separate break window handles breaks,
// so the main window should keep showing whatever view it was on (dashboard).
const effectiveView = $derived(
$currentView === "breakScreen" && !$config.fullscreen_mode
? "dashboard"
: $currentView
);
</script>
<div class="relative h-full bg-black">
{#if $config.background_blobs_enabled}
<BackgroundBlobs accentColor={$config.accent_color} breakColor={$config.break_color} />
{/if}
<Titlebar />
<div
class="relative h-full overflow-hidden origin-top-left"
style="
transform: scale({zoomScale});
width: {100 / zoomScale}%;
height: {100 / zoomScale}%;
"
>
{#if effectiveView === "dashboard"}
<div
class="absolute inset-0"
in:fly={{ x: previousView === "settings" ? -200 : 0, duration: DURATION, easing, opacity: 0 }}
out:fly={{ x: previousView === "dashboard" ? -200 : 0, duration: DURATION * 0.6, easing, opacity: 0 }}
>
<Dashboard />
</div>
{/if}
{#if effectiveView === "breakScreen"}
<div
class="absolute inset-0"
in:scale={{ start: 1.08, duration: DURATION, easing, opacity: 0 }}
out:scale={{ start: 0.95, duration: DURATION * 0.6, easing, opacity: 0 }}
>
<BreakScreen />
</div>
{/if}
{#if effectiveView === "settings"}
<div
class="absolute inset-0"
in:fly={{ x: 200, duration: DURATION, easing, opacity: 0 }}
out:fly={{ x: 200, duration: DURATION * 0.6, easing, opacity: 0 }}
>
<Settings />
</div>
{/if}
{#if effectiveView === "stats"}
<div
class="absolute inset-0"
in:fly={{ y: 200, duration: DURATION, easing, opacity: 0 }}
out:fly={{ y: 200, duration: DURATION * 0.6, easing, opacity: 0 }}
>
<StatsView />
</div>
{/if}
</div>
</div>