Lemonade Stand: browser recreation of the Atari 8-bit BASIC game

Faithful day loop - weather report, cost of lemonade rising over the
summer, glasses/signs/price decisions, thunderstorms, heat waves and
street crews - with the $$ LEMONSVILLE DAILY FINANCIAL REPORT $$ laid
out the way it printed.

The simulation is kept out of React so a season can be run headlessly:
a careful player compounds $2.00 into roughly $22 over twelve days,
while an all-in player goes broke about seven times in ten.

Weather scenes are pixel rectangles in SVG and the music is a small
chiptune engine on the Web Audio API, so there are no binary assets.
The screen is locked to a 4:3 tube and never scrolls.
This commit is contained in:
2026-09-08 15:23:46 -07:00
commit 5f8ad511f0
32 changed files with 3913 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
import { useState } from 'react'
import { Btn, Line } from './Crt'
const PAGES: string[][] = [
[
'HI! WELCOME TO LEMONSVILLE, CALIFORNIA!',
'',
'IN THIS SMALL TOWN YOU ARE IN CHARGE OF',
'RUNNING YOUR OWN LEMONADE STAND. YOU CAN',
'COMPETE WITH AS MANY OTHER PEOPLE AS YOU',
'WISH, BUT HOW MUCH PROFIT YOU MAKE IS UP',
'TO YOU. IF YOU MAKE THE MOST MONEY,',
"YOU'RE THE WINNER!",
'',
'TO MAKE LEMONADE YOU WILL NEED LEMONS,',
'SUGAR, ICE AND PAPER CUPS. THE COST OF A',
'GLASS STARTS AT 2 CENTS AND CLIMBS AS',
'THE SUMMER WEARS ON.',
],
[
'EACH DAY YOU DECIDE THREE THINGS:',
'',
' 1. HOW MANY GLASSES TO MAKE',
' 2. HOW MANY SIGNS TO PUT UP',
' (15 CENTS EACH)',
' 3. WHAT TO CHARGE PER GLASS',
'',
'SIGNS BRING CUSTOMERS, BUT THE FOURTH',
'SIGN HELPS A LOT LESS THAN THE FIRST.',
'',
'WATCH THE WEATHER. A HOT DAY IS WORTH',
'MORE GLASSES AND A HIGHER PRICE. A',
'CLOUDY DAY MIGHT TURN INTO A STORM AND',
'RUIN EVERY GLASS YOU MADE.',
'',
'YOU START WITH $2.00. GOOD LUCK!',
],
]
export function IntroScreen({ onDone }: { onDone: () => void }) {
const [page, setPage] = useState(0)
const last = page === PAGES.length - 1
return (
<div className="stack">
<Line className="center inv-line">HOW TO RUN A LEMONADE STAND</Line>
<Line />
{PAGES[page].map((t, i) => (
<Line key={i}>{t}</Line>
))}
<Line />
<div className="row center">
{page > 0 && <Btn onClick={() => setPage(page - 1)}>BACK</Btn>}
<Btn kind="primary" onClick={() => (last ? onDone() : setPage(page + 1))}>
{last ? 'START' : 'MORE'}
</Btn>
<Line className="dim">
PAGE {page + 1} OF {PAGES.length}
</Line>
</div>
</div>
)
}