From c6c19dbc7719186a4ab08c831d766233fdb154c7 Mon Sep 17 00:00:00 2001 From: Till JS Date: Sun, 12 Apr 2026 18:45:31 +0200 Subject: [PATCH] feat(moodlit): fullscreen mood on click with visual card redesign Clicking a mood now opens it immediately in fullscreen (browser Fullscreen API, z-index above all UI). Preview step removed. Cards redesigned with full gradient backgrounds, live animations, gradient overlays, and hover border highlight. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/lib/modules/moodlit/ListView.svelte | 166 ++++++++++--- .../components/mood/MoodFullscreen.svelte | 225 ++++++++++-------- .../routes/(app)/moodlit/moods/+page.svelte | 184 ++++++++++---- 3 files changed, 403 insertions(+), 172 deletions(-) diff --git a/apps/mana/apps/web/src/lib/modules/moodlit/ListView.svelte b/apps/mana/apps/web/src/lib/modules/moodlit/ListView.svelte index aa1690851..3119e027f 100644 --- a/apps/mana/apps/web/src/lib/modules/moodlit/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/moodlit/ListView.svelte @@ -6,12 +6,13 @@ import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import { BaseListView } from '@mana/shared-ui'; - import type { LocalMood, AnimationType } from './types'; + import type { LocalMood, AnimationType, Mood } from './types'; import { ANIMATIONS } from './types'; import { moodsStore } from './stores/moods.svelte'; import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui'; import { useItemContextMenu } from '$lib/data/item-context-menu.svelte'; import { Trash, Power } from '@mana/shared-icons'; + import MoodFullscreen from './components/mood/MoodFullscreen.svelte'; const moodsQuery = useLiveQueryWithDefault(async () => { const all = await db.table('moods').toArray(); @@ -20,8 +21,16 @@ const moods = $derived(moodsQuery.value); - let activeMoodId = $state(null); - const activeMood = $derived(moods.find((m) => m.id === activeMoodId)); + let fullscreenMood = $state(null); + + function toMood(local: LocalMood): Mood { + return { + id: local.id, + name: local.name, + colors: local.colors, + animationType: local.animation as AnimationType, + }; + } function gradientStyle(colors: string[]): string { if (colors.length === 0) return 'background: #333'; @@ -29,6 +38,25 @@ return `background: linear-gradient(135deg, ${colors.join(', ')})`; } + function getAnimClass(animation: string): string { + switch (animation) { + case 'pulse': + case 'breath': + return 'anim-breath'; + case 'wave': + case 'ocean': + return 'anim-wave'; + case 'candle': + case 'fire': + return 'anim-candle'; + case 'disco': + case 'rave': + return 'anim-disco'; + default: + return 'anim-gradient'; + } + } + // ── Inline create ────────────────────────────────────── let creating = $state(false); let newName = $state(''); @@ -69,11 +97,11 @@ ? [ { id: 'activate', - label: activeMoodId === ctxMenu.state.target.id ? 'Deaktivieren' : 'Aktivieren', + label: 'Aktivieren', icon: Power, action: () => { const target = ctxMenu.state.target; - if (target) activeMoodId = activeMoodId === target.id ? null : target.id; + if (target) fullscreenMood = target; }, }, { id: 'div', label: '', type: 'divider' as const }, @@ -84,10 +112,7 @@ variant: 'danger' as const, action: () => { const target = ctxMenu.state.target; - if (target) { - if (activeMoodId === target.id) activeMoodId = null; - moodsStore.deleteMood(target.id); - } + if (target) moodsStore.deleteMood(target.id); }, }, ] @@ -103,22 +128,6 @@ listClass="grid grid-cols-2 sm:grid-cols-3 gap-2 content-start" > {#snippet toolbar()} - - {#if activeMood} -
-

{activeMood.name}

-
- {:else} -
-

Kein Mood aktiv

-
- {/if} -
{moods.length} Moods @@ -135,10 +144,15 @@
- {newName || 'Vorschau'} +
+ {newName || 'Vorschau'}
@@ -204,20 +218,31 @@ {#snippet item(mood)} {/snippet} +{#if fullscreenMood} + (fullscreenMood = null)} /> +{/if} + + + diff --git a/apps/mana/apps/web/src/lib/modules/moodlit/components/mood/MoodFullscreen.svelte b/apps/mana/apps/web/src/lib/modules/moodlit/components/mood/MoodFullscreen.svelte index 5be9f06b0..5c36a5133 100644 --- a/apps/mana/apps/web/src/lib/modules/moodlit/components/mood/MoodFullscreen.svelte +++ b/apps/mana/apps/web/src/lib/modules/moodlit/components/mood/MoodFullscreen.svelte @@ -5,12 +5,13 @@ interface Props { mood: Mood; + minimal?: boolean; isFavorite?: boolean; onClose: () => void; onFavoriteToggle?: () => void; } - let { mood, isFavorite = false, onClose, onFavoriteToggle }: Props = $props(); + let { mood, minimal = false, isFavorite = false, onClose, onFavoriteToggle }: Props = $props(); let isPlaying = $state(true); let showControls = $state(true); @@ -87,7 +88,7 @@ timerRemaining--; if (timerRemaining <= 0) { stopTimer(); - onClose(); + handleClose(); } }, 1000); } @@ -106,9 +107,16 @@ return `${mins}:${secs.toString().padStart(2, '0')}`; } + async function handleClose() { + if (document.fullscreenElement) { + await document.exitFullscreen().catch(() => {}); + } + onClose(); + } + function handleKeydown(e: KeyboardEvent) { if (e.key === 'Escape') { - onClose(); + handleClose(); } else if (e.key === ' ') { e.preventDefault(); togglePlay(); @@ -116,10 +124,26 @@ } $effect(() => { - showControlsTemporarily(); + if (minimal) { + document.documentElement.requestFullscreen?.().catch(() => {}); + } else { + showControlsTemporarily(); + } + + const onFsChange = () => { + if (minimal && !document.fullscreenElement) { + onClose(); + } + }; + document.addEventListener('fullscreenchange', onFsChange); + return () => { if (controlsTimeout) clearTimeout(controlsTimeout); if (timerInterval) clearInterval(timerInterval); + document.removeEventListener('fullscreenchange', onFsChange); + if (document.fullscreenElement) { + document.exitFullscreen().catch(() => {}); + } }; }); @@ -127,9 +151,10 @@