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:
10
src/App.tsx
10
src/App.tsx
@@ -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
43
src/lib/timeOfDay.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user