73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
import { CLOUD, FOG, HEART_EMPTY, HEART_FULL, HEART_HALF, MOON, PALETTE, RAIN, SNOW, STORM, SUN } from "@/lib/pixel-art";
|
||
import type { Condition } from "@/lib/weather/types";
|
||
|
||
interface PixelArtProps {
|
||
rows: string[];
|
||
size: number; // default rendered width in px (a className can override it)
|
||
label?: string; // omit for decorative art
|
||
className?: string;
|
||
}
|
||
|
||
/** Renders a text-drawn sprite (see lib/pixel-art.ts) as crisp SVG squares. */
|
||
export function PixelArt({ rows, size, label, className }: PixelArtProps) {
|
||
const width = rows[0].length;
|
||
const height = rows.length;
|
||
|
||
const pixels = rows.flatMap((row, y) =>
|
||
[...row].map((char, x) =>
|
||
char === "." ? null : <rect key={`${x}-${y}`} x={x} y={y} width={1} height={1} fill={PALETTE[char]} />,
|
||
),
|
||
);
|
||
|
||
return (
|
||
<svg
|
||
className={className}
|
||
width={size}
|
||
height={(size / width) * height}
|
||
viewBox={`0 0 ${width} ${height}`}
|
||
shapeRendering="crispEdges"
|
||
role={label ? "img" : undefined}
|
||
aria-label={label}
|
||
aria-hidden={label ? undefined : true}
|
||
>
|
||
{pixels}
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
const SKY_ART: Record<Condition, string[]> = {
|
||
clear: SUN,
|
||
clouds: CLOUD,
|
||
rain: RAIN,
|
||
storm: STORM,
|
||
snow: SNOW,
|
||
fog: FOG,
|
||
};
|
||
|
||
/** Picks the sprite for a condition; clear nights get the moon. */
|
||
export function SkyIcon({ condition, isDay = true, size, className }: {
|
||
condition: Condition;
|
||
isDay?: boolean;
|
||
size: number;
|
||
className?: string;
|
||
}) {
|
||
const rows = condition === "clear" && !isDay ? MOON : SKY_ART[condition];
|
||
return <PixelArt rows={rows} size={size} className={className} />;
|
||
}
|
||
|
||
/** A row of five hearts showing a 0–5 score in half steps. */
|
||
export function HeartMeter({ hearts }: { hearts: number }) {
|
||
const sprites = [0, 1, 2, 3, 4].map((i) => {
|
||
if (hearts >= i + 1) return HEART_FULL;
|
||
if (hearts >= i + 0.5) return HEART_HALF;
|
||
return HEART_EMPTY;
|
||
});
|
||
|
||
return (
|
||
<div className="flex gap-[5px]" role="img" aria-label={`Comfort: ${hearts} of 5 hearts`}>
|
||
{sprites.map((rows, i) => (
|
||
<PixelArt key={i} rows={rows} size={22} />
|
||
))}
|
||
</div>
|
||
);
|
||
}
|