import { For, type JSX } from "solid-js"; /** * Pixel-art campfire sprite, faithful to the app's CampfireFrames: a 14×14 * grid where the log base never moves and only the flame and sparks animate. * `state` picks the frame set and cadence — idle simmers, running dances, * ready exhales, error pulses dead embers. */ const FIRE_PALETTE: Record = { o: "#e7873a", y: "#ffce70", w: "#ffffff", b: "#8b5a2b", d: "#5a3a1b", s: "#ffd166", r: "#c43a30", R: "#f25240", x: "#ff7864", m: "rgba(255,255,255,0.5)", k: "rgba(255,255,255,0.26)", e: "#a8582a", E: "#d87636", B: "#4890fc", L: "#92c7ff", C: "#e9f8ff", S: "#bfe0ff" }; interface CampfireFrame { rows: string[]; } const IDLE_FRAMES: CampfireFrame[] = [ { rows: [ "..............", "..............", "..............", "..............", "..............", "..............", "..............", ".....oyo......", "....oyyyo.....", "..dddeeeeeEd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ "..............", "..............", ".........k....", "..............", "..............", "..............", "..............", "......oyo.....", "....oyyyo.....", "..dddEEEEEEd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] } ]; const RUN_FRAMES: CampfireFrame[] = [ { rows: [ "..............", "..............", ".....s.yy.....", ".....oyyo.....", "....oywwyo....", "...oyywwyyo...", "...oyywwyyo...", "...oyywwyyo...", "....oywwyo....", "..dddddddddd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ "..............", ".....s........", ".....yy.......", ".....oyyo.....", "....oywwyo....", "...oyywwyyo...", "...oyywwyyo...", "...oyywwyyo...", "....oywwyo....", "..dddddddddd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] } ]; const READY_FRAMES: CampfireFrame[] = [ { rows: [ "..............", "..............", "..............", "..............", "..............", "......yy......", ".....oyyo.....", "....oyyyyo....", "....oyyyyo....", "..dddyyyyyyd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ "..............", "..............", "..............", "..............", "........m.....", ".....yy.......", ".....oyyo.....", "....oyyyyo....", "....oyyyyo....", "..dddyyyyyyd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] } ]; /* App CampfireFrames.ques0-5: cool blue flame, tip dips, sparks spit. */ const QUES_FRAMES: CampfireFrame[] = [ { rows: [ "..............", "..............", ".....S.LL.....", ".....BLLB.....", "....BLCCLB....", "...BLLCCLLB...", "...BLLCCLLB...", "...BLLCCLLB...", "....BLCCLB....", "..dddccccccd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ "..............", ".....S........", ".....LL.......", ".....BLLB.....", "....BLCCLB....", "...BLLCCLLB...", "...BLLCCLLB...", "...BLLCCLLB...", "....BLCCLB....", "..dddccccccd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ "..............", ".....S........", "......LL......", ".....BLLB.....", "....BLCCLB....", "...BLLCCLLB...", "...BLLCCLLB...", "...BLLCCLLB...", "....BLCCLB....", "..dddccccccd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ ".....S........", "..............", "......LL......", ".....BLLB.....", "....BLCCLB....", "...BLLCCLLB...", "...BLLCCLLB...", "...BLLCCLLB...", "....BLCCLB....", "..dddccccccd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ ".....S........", "..............", ".......LL.....", ".....BLLB.....", "....BLCCLB....", "...BLLCCLLB...", "...BLLCCLLB...", "...BLLCCLLB...", "....BLCCLB....", "..dddccccccd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ "........S.....", "..............", ".......LL.....", ".....BLLB.....", "....BLCCLB....", "...BLLCCLLB...", "...BLLCCLLB...", "...BLLCCLLB...", "....BLCCLB....", "..dddccccccd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] } ]; const ERROR_FRAMES: CampfireFrame[] = [ { rows: [ "..............", "..............", "..............", "..............", "..............", "..............", "..............", "..............", "..............", "..dddrrrrrrd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] }, { rows: [ "........x.....", "..............", ".....x........", "..............", "..............", "..............", "..............", "..............", "..............", "..dddRRRRRRd..", "..dbbbbbbbbd..", "..............", "..............", ".............." ] } ]; export type CampfireState = "idle" | "running" | "ready" | "question" | "error"; const STATE_FRAMES: Record = { idle: IDLE_FRAMES, running: RUN_FRAMES, ready: READY_FRAMES, question: QUES_FRAMES, error: ERROR_FRAMES }; const STATE_FPS: Record = { idle: 2, running: 9, ready: 4, question: 4.5, error: 4 }; export function Campfire(props: { state: CampfireState; pixel?: number }) { const pixel = () => props.pixel ?? 3; return (
{/* Slot phase lives in the keyframes per layer (campfire-slot-N-x); delay-offset layers blank a wrap frame under WebKit. */} {(frame, i) => { const n = STATE_FRAMES[props.state].length; const phase = String.fromCharCode(97 + (i() % 26)); return (
{(row) => (
{(ch) => ( )}
)}
); }}
); }