diff --git a/apps/clock/apps/web/package.json b/apps/clock/apps/web/package.json index 4d480150b..c6ae5a7cf 100644 --- a/apps/clock/apps/web/package.json +++ b/apps/clock/apps/web/package.json @@ -17,7 +17,10 @@ "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tailwindcss/vite": "^4.1.7", + "@types/d3": "^7.4.3", "@types/node": "^20.0.0", + "@types/topojson-client": "^3.1.5", + "@types/topojson-specification": "^1.0.5", "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", "svelte": "^5.0.0", @@ -42,8 +45,10 @@ "@manacore/shared-theme": "workspace:*", "@manacore/shared-theme-ui": "workspace:*", "@manacore/shared-ui": "workspace:*", + "d3": "^7.9.0", "svelte-dnd-action": "^0.9.68", - "svelte-i18n": "^4.0.1" + "svelte-i18n": "^4.0.1", + "topojson-client": "^3.1.0" }, "type": "module" } diff --git a/apps/clock/apps/web/src/lib/components/WorldMap.svelte b/apps/clock/apps/web/src/lib/components/WorldMap.svelte new file mode 100644 index 000000000..06f055aa6 --- /dev/null +++ b/apps/clock/apps/web/src/lib/components/WorldMap.svelte @@ -0,0 +1,453 @@ + + +
+ + + + + + {#each [-60, -30, 0, 30, 60] as lat} + {@const y = ((90 - lat) / 180) * height} + + {/each} + {#each [-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180] as lng} + {@const x = ((lng + 180) / 360) * width} + + {/each} + + + + + + + + + + + + + + + + + + + + + { + const hours = currentTime.getUTCHours() + currentTime.getUTCMinutes() / 60; + const sunLng = -((hours - 12) * 15); + const nightCenterX = (((sunLng + 180 + 180) % 360) / 360) * width; + return nightCenterX - width / 2; + })()} + y="0" + width={width / 2} + {height} + class="night-overlay" + /> + + + {#each POPULAR_TIMEZONES as city} + {@const pos = latLngToXY(city.lat, city.lng)} + {@const isSelected = selectedCities.includes(city.timezone)} + {@const isDay = isDaytime(city.lat, city.lng)} + {@const isHovered = hoveredCity?.timezone === city.timezone} + handleCityClick(city)} + onkeydown={(e) => e.key === 'Enter' && handleCityClick(city)} + onpointerenter={() => (hoveredCity = city)} + onpointerleave={() => (hoveredCity = null)} + > + + + {#if isSelected} + + {/if} + {#if isHovered} + + {/if} + + + {/each} + + + + {#if hoveredCity} + {@const pos = latLngToXY(hoveredCity.lat, hoveredCity.lng)} + {@const offset = getTimezoneOffset(hoveredCity.timezone)} + {@const isDay = isDaytime(hoveredCity.lat, hoveredCity.lng)} +
+
+ {hoveredCity.city} + {isDay ? '☀️' : '🌙'} +
+
{getTimeForTimezone(hoveredCity.timezone)}
+
+ {getDateForTimezone(hoveredCity.timezone)} + {offset} +
+
+ {/if} + + +
+
+ + Tag +
+
+ + Nacht +
+
+ + Ausgewählt +
+
+
+ + diff --git a/apps/clock/packages/shared/src/constants/index.ts b/apps/clock/packages/shared/src/constants/index.ts index eb3b327e2..205c25895 100644 --- a/apps/clock/packages/shared/src/constants/index.ts +++ b/apps/clock/packages/shared/src/constants/index.ts @@ -1,29 +1,93 @@ -// Popular timezones with city names +// Popular timezones with city names and coordinates for map display export const POPULAR_TIMEZONES = [ - { timezone: 'America/New_York', city: 'New York', region: 'Americas' }, - { timezone: 'America/Los_Angeles', city: 'Los Angeles', region: 'Americas' }, - { timezone: 'America/Chicago', city: 'Chicago', region: 'Americas' }, - { timezone: 'America/Toronto', city: 'Toronto', region: 'Americas' }, - { timezone: 'America/Sao_Paulo', city: 'São Paulo', region: 'Americas' }, - { timezone: 'Europe/London', city: 'London', region: 'Europe' }, - { timezone: 'Europe/Paris', city: 'Paris', region: 'Europe' }, - { timezone: 'Europe/Berlin', city: 'Berlin', region: 'Europe' }, - { timezone: 'Europe/Rome', city: 'Rome', region: 'Europe' }, - { timezone: 'Europe/Madrid', city: 'Madrid', region: 'Europe' }, - { timezone: 'Europe/Amsterdam', city: 'Amsterdam', region: 'Europe' }, - { timezone: 'Europe/Vienna', city: 'Vienna', region: 'Europe' }, - { timezone: 'Europe/Zurich', city: 'Zurich', region: 'Europe' }, - { timezone: 'Europe/Moscow', city: 'Moscow', region: 'Europe' }, - { timezone: 'Asia/Tokyo', city: 'Tokyo', region: 'Asia' }, - { timezone: 'Asia/Shanghai', city: 'Shanghai', region: 'Asia' }, - { timezone: 'Asia/Hong_Kong', city: 'Hong Kong', region: 'Asia' }, - { timezone: 'Asia/Singapore', city: 'Singapore', region: 'Asia' }, - { timezone: 'Asia/Seoul', city: 'Seoul', region: 'Asia' }, - { timezone: 'Asia/Mumbai', city: 'Mumbai', region: 'Asia' }, - { timezone: 'Asia/Dubai', city: 'Dubai', region: 'Asia' }, - { timezone: 'Australia/Sydney', city: 'Sydney', region: 'Oceania' }, - { timezone: 'Australia/Melbourne', city: 'Melbourne', region: 'Oceania' }, - { timezone: 'Pacific/Auckland', city: 'Auckland', region: 'Oceania' }, + { + timezone: 'America/New_York', + city: 'New York', + region: 'Americas', + lat: 40.7128, + lng: -74.006, + }, + { + timezone: 'America/Los_Angeles', + city: 'Los Angeles', + region: 'Americas', + lat: 34.0522, + lng: -118.2437, + }, + { timezone: 'America/Chicago', city: 'Chicago', region: 'Americas', lat: 41.8781, lng: -87.6298 }, + { timezone: 'America/Toronto', city: 'Toronto', region: 'Americas', lat: 43.6532, lng: -79.3832 }, + { + timezone: 'America/Sao_Paulo', + city: 'São Paulo', + region: 'Americas', + lat: -23.5505, + lng: -46.6333, + }, + { + timezone: 'America/Mexico_City', + city: 'Mexico City', + region: 'Americas', + lat: 19.4326, + lng: -99.1332, + }, + { + timezone: 'America/Buenos_Aires', + city: 'Buenos Aires', + region: 'Americas', + lat: -34.6037, + lng: -58.3816, + }, + { + timezone: 'America/Vancouver', + city: 'Vancouver', + region: 'Americas', + lat: 49.2827, + lng: -123.1207, + }, + { timezone: 'Europe/London', city: 'London', region: 'Europe', lat: 51.5074, lng: -0.1278 }, + { timezone: 'Europe/Paris', city: 'Paris', region: 'Europe', lat: 48.8566, lng: 2.3522 }, + { timezone: 'Europe/Berlin', city: 'Berlin', region: 'Europe', lat: 52.52, lng: 13.405 }, + { timezone: 'Europe/Rome', city: 'Rome', region: 'Europe', lat: 41.9028, lng: 12.4964 }, + { timezone: 'Europe/Madrid', city: 'Madrid', region: 'Europe', lat: 40.4168, lng: -3.7038 }, + { timezone: 'Europe/Amsterdam', city: 'Amsterdam', region: 'Europe', lat: 52.3676, lng: 4.9041 }, + { timezone: 'Europe/Vienna', city: 'Vienna', region: 'Europe', lat: 48.2082, lng: 16.3738 }, + { timezone: 'Europe/Zurich', city: 'Zurich', region: 'Europe', lat: 47.3769, lng: 8.5417 }, + { timezone: 'Europe/Moscow', city: 'Moscow', region: 'Europe', lat: 55.7558, lng: 37.6173 }, + { timezone: 'Europe/Stockholm', city: 'Stockholm', region: 'Europe', lat: 59.3293, lng: 18.0686 }, + { timezone: 'Europe/Istanbul', city: 'Istanbul', region: 'Europe', lat: 41.0082, lng: 28.9784 }, + { timezone: 'Asia/Tokyo', city: 'Tokyo', region: 'Asia', lat: 35.6762, lng: 139.6503 }, + { timezone: 'Asia/Shanghai', city: 'Shanghai', region: 'Asia', lat: 31.2304, lng: 121.4737 }, + { timezone: 'Asia/Hong_Kong', city: 'Hong Kong', region: 'Asia', lat: 22.3193, lng: 114.1694 }, + { timezone: 'Asia/Singapore', city: 'Singapore', region: 'Asia', lat: 1.3521, lng: 103.8198 }, + { timezone: 'Asia/Seoul', city: 'Seoul', region: 'Asia', lat: 37.5665, lng: 126.978 }, + { timezone: 'Asia/Mumbai', city: 'Mumbai', region: 'Asia', lat: 19.076, lng: 72.8777 }, + { timezone: 'Asia/Dubai', city: 'Dubai', region: 'Asia', lat: 25.2048, lng: 55.2708 }, + { timezone: 'Asia/Bangkok', city: 'Bangkok', region: 'Asia', lat: 13.7563, lng: 100.5018 }, + { timezone: 'Asia/Jakarta', city: 'Jakarta', region: 'Asia', lat: -6.2088, lng: 106.8456 }, + { timezone: 'Australia/Sydney', city: 'Sydney', region: 'Oceania', lat: -33.8688, lng: 151.2093 }, + { + timezone: 'Australia/Melbourne', + city: 'Melbourne', + region: 'Oceania', + lat: -37.8136, + lng: 144.9631, + }, + { + timezone: 'Pacific/Auckland', + city: 'Auckland', + region: 'Oceania', + lat: -36.8485, + lng: 174.7633, + }, + { timezone: 'Africa/Cairo', city: 'Cairo', region: 'Africa', lat: 30.0444, lng: 31.2357 }, + { + timezone: 'Africa/Johannesburg', + city: 'Johannesburg', + region: 'Africa', + lat: -26.2041, + lng: 28.0473, + }, + { timezone: 'Africa/Lagos', city: 'Lagos', region: 'Africa', lat: 6.5244, lng: 3.3792 }, ] as const; // Available alarm sounds @@ -48,6 +112,16 @@ export const QUICK_TIMER_PRESETS = [ { label: '1 hour', seconds: 3600 }, ] as const; +// Default alarm presets (like iOS Clock app) +export const DEFAULT_ALARM_PRESETS = [ + { time: '06:00', label: 'Früh aufstehen', labelEN: 'Wake up early' }, + { time: '07:00', label: 'Aufwachen', labelEN: 'Wake up' }, + { time: '08:00', label: 'Morgen', labelEN: 'Morning' }, + { time: '12:00', label: 'Mittag', labelEN: 'Noon' }, + { time: '18:00', label: 'Feierabend', labelEN: 'End of work' }, + { time: '22:00', label: 'Schlafenszeit', labelEN: 'Bedtime' }, +] as const; + // Pomodoro presets export const POMODORO_PRESETS = [ {