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:
118
src/App.svelte
Normal file
118
src/App.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user