-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e748072
commit 26d727b
Showing
9 changed files
with
222 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import clsx from "clsx"; | ||
import { useMemo } from "react"; | ||
|
||
interface MoonIconProps { | ||
lat: number; | ||
phase: number; | ||
} | ||
|
||
const ease = (x: number) => 1 - Math.sqrt(1 - Math.pow(x, 2)); | ||
|
||
export const MoonIcon = ({ lat, phase }: MoonIconProps) => { | ||
const isGibbous = useMemo(() => phase >= 0.25 && phase < 0.75, [phase]); | ||
|
||
const scale = useMemo(() => { | ||
const min = 1; | ||
const max = 4; | ||
|
||
if (phase < 0.25) { | ||
return min + (phase / 0.25) * (max - min); | ||
} else if (phase < 0.5) { | ||
return max - ((phase - 0.25) / 0.25) * (max - min); | ||
} else if (phase < 0.75) { | ||
return min + ((phase - 0.5) / 0.25) * (max - min); | ||
} else { | ||
return max - ((phase - 0.75) / 0.25) * (max - min); | ||
} | ||
}, [phase]); | ||
|
||
const translation = useMemo(() => { | ||
if (phase < 0.25) { | ||
const min = 32; | ||
const max = 36; | ||
|
||
return min + (phase / 0.25) * (max - min); | ||
} else if (phase < 0.5) { | ||
const min = 64; | ||
const max = 36; | ||
|
||
return max - ease((phase - 0.25) / 0.25) * (max - min); | ||
} else if (phase < 0.75) { | ||
const min = 64; | ||
const max = 68; | ||
|
||
return min + ((phase - 0.5) / 0.25) * (max - min); | ||
} else { | ||
const min = 96; | ||
const max = 68; | ||
|
||
return max - ease((phase - 0.75) / 0.25) * (max - min); | ||
} | ||
}, [phase]); | ||
|
||
const maskStyles = "w-8 h-8 rounded-full"; | ||
|
||
return ( | ||
<div | ||
className="relative flex justify-center items-center h-8 w-8 rounded-full pointer-events-none overflow-hidden origin-center" | ||
style={{ rotate: `-${90 - lat}deg` }} | ||
> | ||
{/* Background */} | ||
<div | ||
className={clsx( | ||
"h-8 w-8 rounded-full transition-all", | ||
!isGibbous ? "bg-zinc-700" : "bg-primary" | ||
)} | ||
/> | ||
|
||
<div | ||
className="absolute left-8 flex origin-left transition-all" | ||
style={{ | ||
transform: `scale(${scale}) translateX(${-translation}px)`, | ||
}} | ||
> | ||
{/* Right mask */} | ||
<div className={clsx(maskStyles, "bg-primary")} /> | ||
|
||
{/* Center mask */} | ||
<div className={clsx(maskStyles, "bg-zinc-700")} /> | ||
|
||
{/* Left mask */} | ||
<div className={clsx(maskStyles, "bg-primary")} /> | ||
</div> | ||
|
||
{/* Shadow overlay */} | ||
<div className="absolute w-8 h-8 rounded-full shadow-center-inset-2 shadow-primary" /> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import clsx from "clsx"; | ||
import { useWeatherStore } from "../../stores/weather.store"; | ||
import { getAdjustedTime } from "../../utils/getAdjustedTime"; | ||
import { getMoonIllumination } from "../../utils/getMoonIllumination"; | ||
import { getMoonPhase } from "../../utils/getMoonPhase"; | ||
import { MoonIcon } from "../atoms/MoonIcon"; | ||
import { ScrollButtons } from "../atoms/ScrollButtons"; | ||
|
||
export const MoonPhaseCard = () => { | ||
const { data } = useWeatherStore(); | ||
|
||
return ( | ||
<li | ||
className="col-span-2 flex flex-col gap-1 py-4 bg-primary text-inverted-text dark:text-text rounded-xl text-sm" | ||
tabIndex={0} | ||
> | ||
<span className="px-4">Moon phase</span> | ||
|
||
<div className="flex gap-4 pl-4"> | ||
<div className="flex-shrink-0 flex flex-col justify-between h-full"> | ||
<div className="flex flex-col"> | ||
<span className="text-xs text-inverted-text/75 dark:text-text/75"> | ||
Moonrise | ||
</span> | ||
<span className="text-2xl"> | ||
{new Date( | ||
getAdjustedTime(data.timezone_offset, data.daily[0].moonrise) | ||
).toLocaleTimeString("en-gb", { timeStyle: "short" })} | ||
</span> | ||
</div> | ||
<div className="flex flex-col"> | ||
<span className="text-xs text-inverted-text/75 dark:text-text/75"> | ||
Moonset | ||
</span> | ||
<span className="text-2xl"> | ||
{new Date( | ||
getAdjustedTime(data.timezone_offset, data.daily[0].moonset) | ||
).toLocaleTimeString("en-gb", { timeStyle: "short" })} | ||
</span> | ||
</div> | ||
</div> | ||
|
||
<ScrollButtons> | ||
<ul | ||
className={clsx( | ||
"flex justify-between items-center gap-4 max-w-full h-full overflow-x-scroll scrollbar-none snap-x snap-proximity transition-all ", | ||
data.isFallback && "blur" | ||
)} | ||
aria-label="Moon phase list" | ||
tabIndex={0} | ||
> | ||
{data.daily.map((day, index) => ( | ||
<li | ||
className="flex-1 flex flex-col gap-1 justify-center items-center" | ||
key={`Moon__Cycle__${day.dt}__${day.moon_phase}__${index}`} | ||
> | ||
<span className="text-xs text-center text-inverted-text/75 dark:text-text/75"> | ||
{getMoonPhase(day.moon_phase)} | ||
</span> | ||
|
||
<MoonIcon lat={data.lat} phase={day.moon_phase} /> | ||
|
||
<span | ||
aria-label={`${getMoonIllumination( | ||
day.moon_phase | ||
).toFixed()}% illumination`} | ||
className="text-xs" | ||
> | ||
{getMoonIllumination(day.moon_phase).toFixed()}% | ||
</span> | ||
|
||
<time className="text-xs text-inverted-text/75 dark:text-text/75"> | ||
{index === 0 | ||
? "Today" | ||
: new Date( | ||
getAdjustedTime(data.timezone_offset, day.dt) | ||
).toLocaleDateString("en-gb", { | ||
day: "numeric", | ||
month: "short", | ||
})} | ||
</time> | ||
</li> | ||
))} | ||
</ul> | ||
</ScrollButtons> | ||
</div> | ||
</li> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export const getMoonIllumination = (phase: number) => { | ||
const min = 0; | ||
const max = 100; | ||
const phasePercent = phase * 100; | ||
|
||
if (phase <= 0.5) { | ||
return (min + (phasePercent / 50) * (max - min)); | ||
} else { | ||
return max - ((phasePercent - 50) / 50) * (max - min); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export const getMoonPhase = (phase: number) => { | ||
if (phase === 0 || phase === 100) { | ||
return "New moon"; | ||
} | ||
|
||
if (phase === 0.25) return "First quarter"; | ||
|
||
if (phase === 0.5) return "Full Moon"; | ||
|
||
if (phase === 0.75) return "Last quarter"; | ||
|
||
if (phase > 0 && phase < 0.25) return "Waxing crescent"; | ||
if (phase > 0.25 && phase < 0.5) return "Waxing gibbous"; | ||
if (phase > 0.5 && phase < 0.75) return "Waning gibbous"; | ||
if (phase > 0.75 && phase < 1) return "Waning crescent"; | ||
|
||
return ""; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters