feat: tint app background by time of day (F-5)

Adds dawn/day/dusk/night palette derived from device-local hour. Root
background fades over 1.5s on transitions; recalculated every 5 minutes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-04 03:46:23 +09:00
parent b1f7dbf216
commit c638679502
2 changed files with 50 additions and 3 deletions

View File

@@ -15,6 +15,7 @@ import { TutorialOverlay } from './components/tutorial/TutorialOverlay';
import { useIdleTick } from './hooks/useIdleTick';
import { useGameStore } from './store/useGameStore';
import { trackGameEvent } from './platform/analytics';
import { useTimeOfDay, getPalette } from './lib/timeOfDay';
const legendaryShake = keyframes`
0%, 100% { transform: translate3d(0, 0, 0); filter: none; }
@@ -25,12 +26,12 @@ const legendaryShake = keyframes`
75% { transform: translate3d(-2px, 1px, 0); }
`;
const rootStyle = (shaking: boolean) => css`
const rootStyle = (shaking: boolean, background: string) => css`
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f7f8fa;
background-color: ${background};
overflow: hidden;
font-family:
'Pretendard',
@@ -38,6 +39,7 @@ const rootStyle = (shaking: boolean) => css`
BlinkMacSystemFont,
sans-serif;
animation: ${shaking ? css`${legendaryShake} 0.42s ease-out` : 'none'};
transition: background-color 1.5s ease-in-out;
`;
const contentStyle = css`
@@ -51,6 +53,8 @@ export function App() {
const [shaking, setShaking] = useState(false);
const [showIntro, setShowIntro] = useState(() => localStorage.getItem('firstspark-intro-seen') !== '1');
useIdleTick();
const timeOfDay = useTimeOfDay();
const palette = getPalette(timeOfDay);
const handleIntroDone = useCallback(() => {
localStorage.setItem('firstspark-intro-seen', '1');
@@ -79,7 +83,7 @@ export function App() {
}, []);
return (
<div css={rootStyle(shaking)}>
<div css={rootStyle(shaking, palette.background)}>
{showIntro && (
<IntroSplash
onDone={handleIntroDone}

43
src/lib/timeOfDay.ts Normal file
View File

@@ -0,0 +1,43 @@
import { useEffect, useState } from 'react';
export type TimeOfDay = 'dawn' | 'day' | 'dusk' | 'night';
export interface TimeOfDayPalette {
background: string;
contentTint: string;
}
const PALETTES: Record<TimeOfDay, TimeOfDayPalette> = {
dawn: { background: '#fff6ec', contentTint: 'rgba(255, 196, 138, 0.05)' },
day: { background: '#f7f8fa', contentTint: 'rgba(255, 255, 255, 0)' },
dusk: { background: '#fff0e6', contentTint: 'rgba(255, 138, 92, 0.06)' },
night: { background: '#101728', contentTint: 'rgba(124, 138, 255, 0.04)' },
};
export function getTimeOfDay(date: Date = new Date()): TimeOfDay {
const h = date.getHours();
if (h >= 5 && h < 9) return 'dawn';
if (h >= 9 && h < 17) return 'day';
if (h >= 17 && h < 20) return 'dusk';
return 'night';
}
export function getPalette(tod: TimeOfDay): TimeOfDayPalette {
return PALETTES[tod];
}
const TICK_MS = 5 * 60 * 1000;
export function useTimeOfDay(): TimeOfDay {
const [tod, setTod] = useState<TimeOfDay>(() => getTimeOfDay());
useEffect(() => {
const id = window.setInterval(() => {
setTod((prev) => {
const next = getTimeOfDay();
return next === prev ? prev : next;
});
}, TICK_MS);
return () => window.clearInterval(id);
}, []);
return tod;
}