feat: Phases 4-7 — Full Feature Expansion (26 features)

Phase 4 — New Widget Types:
- Clock/Weather, System Stats, RSS/Feed, Calendar, Markdown,
  Metric/Counter, Link Group, Camera/Stream widgets
- Backend services with caching for each data source
- Full creation form with dynamic config fields per type

Phase 5 — Visual & Styling Enhancements:
- Glassmorphism card style (solid/glass/outline)
- Board-level themes with per-board hue/saturation
- Animated SVG status rings replacing static dots
- Card size options (compact/medium/large)
- Custom CSS injection (admin + per-board, sanitized)
- Wallpaper backgrounds with blur/overlay/parallax

Phase 6 — Functional Features:
- Favorites bar with drag-and-drop reordering
- Recent apps tracking with privacy toggle
- Uptime dashboard page (/status, guest-accessible)
- Notifications system (Discord/Slack/Telegram/HTTP webhooks)
- App tags with filtering in board view
- Multi-URL app cards with expandable sub-links
- Personal API tokens with scoped permissions
- Audit log with retention and admin viewer

Phase 7 — Quality of Life:
- Onboarding wizard (5-step first-launch setup)
- App URL health preview with favicon/title detection
- Board templates (4 built-in + custom import/export)
- Keyboard shortcut overlay (j/k nav, 1-9 boards, ? help)

212 files changed, 15641 insertions, 980 deletions.
Build, lint, type check, and 222 tests all pass.
This commit is contained in:
2026-03-25 14:18:10 +03:00
parent 8d7847889e
commit 1c0a7cb850
212 changed files with 15642 additions and 981 deletions
@@ -3,6 +3,15 @@
import MeshGradient from './MeshGradient.svelte';
import ParticleField from './ParticleField.svelte';
import AuroraEffect from './AuroraEffect.svelte';
import WallpaperBackground from './WallpaperBackground.svelte';
interface Props {
wallpaperUrl?: string | null;
wallpaperBlur?: number;
wallpaperOverlay?: number;
}
let { wallpaperUrl = null, wallpaperBlur = 0, wallpaperOverlay = 0.3 }: Props = $props();
</script>
{#if theme.backgroundType !== 'none'}
@@ -13,6 +22,8 @@
<ParticleField />
{:else if theme.backgroundType === 'aurora'}
<AuroraEffect />
{:else if theme.backgroundType === 'wallpaper' && wallpaperUrl}
<WallpaperBackground url={wallpaperUrl} blur={wallpaperBlur} overlayOpacity={wallpaperOverlay} />
{/if}
</div>
{/if}
@@ -0,0 +1,62 @@
<script lang="ts">
interface Props {
url: string;
blur?: number;
overlayOpacity?: number;
parallax?: boolean;
position?: 'fixed' | 'scroll';
}
let {
url,
blur = 0,
overlayOpacity = 0.3,
parallax = false,
position = 'fixed'
}: Props = $props();
let loadError = $state(false);
let loaded = $state(false);
function handleLoad() {
loaded = true;
loadError = false;
}
function handleError() {
loadError = true;
loaded = false;
}
const positionClass = $derived(position === 'fixed' ? 'fixed' : 'absolute');
const blurValue = $derived(`${Math.max(0, Math.min(20, blur))}px`);
const overlayAlpha = $derived(Math.max(0, Math.min(1, overlayOpacity)));
</script>
{#if !loadError}
<div
class="pointer-events-none {positionClass} inset-0 z-0 overflow-hidden"
aria-hidden="true"
>
<!-- Wallpaper image -->
<img
src={url}
alt=""
onload={handleLoad}
onerror={handleError}
class="h-full w-full object-cover transition-opacity duration-500"
class:opacity-0={!loaded}
class:opacity-100={loaded}
style="filter: blur({blurValue});{parallax ? ' transform: translateZ(0); will-change: transform;' : ''}"
draggable="false"
/>
<!-- Overlay -->
{#if overlayAlpha > 0}
<div
class="absolute inset-0"
style="background: rgba(0, 0, 0, {overlayAlpha});"
></div>
{/if}
</div>
{/if}