From 74e2dabf76bd827fed10d754f5d793de94a1ba71 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Fri, 12 Dec 2025 03:05:53 +0100 Subject: [PATCH] feat(calendar): add moon phases to DateStrip component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add suncalc library for accurate astronomical moon phase calculation - Display moon phase emojis (🌑🌓🌕🌗) on significant dates - Show new moon, first quarter, full moon, and last quarter - Style moon indicators centered above each day - Add hover effects to "Heute" button 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/calendar/apps/web/package.json | 4 +- .../lib/components/calendar/DateStrip.svelte | 148 ++++++++++-------- 2 files changed, 90 insertions(+), 62 deletions(-) diff --git a/apps/calendar/apps/web/package.json b/apps/calendar/apps/web/package.json index dfd8724e3..f9f78a82f 100644 --- a/apps/calendar/apps/web/package.json +++ b/apps/calendar/apps/web/package.json @@ -19,6 +19,7 @@ "@tailwindcss/vite": "^4.1.7", "@types/d3-force": "^3.0.0", "@types/node": "^20.0.0", + "@types/suncalc": "^1.9.2", "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", "svelte": "^5.0.0", @@ -31,7 +32,6 @@ "dependencies": { "@calendar/shared": "workspace:*", "@manacore/shared-auth": "workspace:*", - "@manacore/shared-types": "workspace:*", "@manacore/shared-auth-ui": "workspace:*", "@manacore/shared-branding": "workspace:*", "@manacore/shared-feedback-service": "workspace:*", @@ -44,12 +44,14 @@ "@manacore/shared-tailwind": "workspace:*", "@manacore/shared-theme": "workspace:*", "@manacore/shared-theme-ui": "workspace:*", + "@manacore/shared-types": "workspace:*", "@manacore/shared-ui": "workspace:*", "@manacore/shared-utils": "workspace:*", "@neodrag/svelte": "^2.3.3", "d3-force": "^3.0.0", "date-fns": "^4.1.0", "lucide-svelte": "^0.559.0", + "suncalc": "^1.9.0", "svelte-dnd-action": "^0.9.68", "svelte-i18n": "^4.0.1" }, diff --git a/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte b/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte index cb3defb5d..c5cb6daa4 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/DateStrip.svelte @@ -11,6 +11,42 @@ } from 'date-fns'; import { de } from 'date-fns/locale'; import { onMount, tick } from 'svelte'; + import SunCalc from 'suncalc'; + + // Moon phase emojis (8 phases) + const MOON_EMOJIS = ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘']; + + // Get moon emoji for a date + function getMoonEmoji(date: Date): string { + const moonData = SunCalc.getMoonIllumination(date); + // phase: 0 = new moon, 0.25 = first quarter, 0.5 = full moon, 0.75 = last quarter + const phaseIndex = Math.floor(moonData.phase * 8) % 8; + return MOON_EMOJIS[phaseIndex]; + } + + // Check if this is a significant moon phase (new, first quarter, full, last quarter) + function isSignificantMoonPhase(date: Date): { significant: boolean; emoji: string } { + const moonData = SunCalc.getMoonIllumination(date); + const phase = moonData.phase; + // Lunar cycle is ~29.53 days, so 1 day = ~0.0339 + // Use half a day tolerance (~0.017) to ensure only 1 day is marked + const tolerance = 0.017; + + if (phase < tolerance || phase > 1 - tolerance) { + return { significant: true, emoji: '🌑' }; // New moon + } + if (Math.abs(phase - 0.25) < tolerance) { + return { significant: true, emoji: '🌓' }; // First quarter + } + if (Math.abs(phase - 0.5) < tolerance) { + return { significant: true, emoji: '🌕' }; // Full moon + } + if (Math.abs(phase - 0.75) < tolerance) { + return { significant: true, emoji: '🌗' }; // Last quarter + } + + return { significant: false, emoji: '' }; + } // Reactive view range - needed to trigger re-renders let viewRange = $derived(viewStore.viewRange); @@ -166,25 +202,14 @@
+ {#if !isTodayVisible} + + {/if} +
- +
{visibleMonth} - {#if !isTodayVisible} - - {/if}
@@ -197,6 +222,7 @@ {@const dayIsRangeStart = isSameDay(day, viewRange.start)} {@const dayIsRangeEnd = isSameDay(day, viewRange.end)} {@const isFirstOfMonth = day.getDate() === 1} + {@const moonPhase = isSignificantMoonPhase(day)} {#if isFirstOfMonth}
{/if} @@ -214,6 +240,9 @@ ? 'background: #3b82f6; color: white; border-radius: 10px; font-weight: 700; box-shadow: 0 2px 8px rgba(59, 130, 246, 0.4);' : ''} > + {#if moonPhase.significant} + {moonPhase.emoji} + {/if} {format(day, 'EE', { locale: de })} @@ -229,16 +258,38 @@