A small, static, bilingual (English / 繁體中文) web app for eating: a eatery map and a set of recipes. No build step, no framework — plain HTML, CSS, and vanilla JS that loads its content from JSON over fetch.
https://r0963033043.github.io/foodie
Hosted on GitHub Pages from the gh-pages branch.
| Page | What it does |
|---|---|
index.html |
Landing page — choose Eateries (map) or Cook (recipes). |
maps.html |
Leaflet map + eatery list with filters and a "near me" sort. |
recipe.html |
Grid of dishes to cook. |
dish.html |
A single recipe (ingredients, steps) with serving scaling. |
- Map + list stay in sync. Click a card to open that eatery's Menu page (
menu.html), which links out to Google Maps, Reserve, and Web / YouTube searches. - Filters: city, district, shoppingDistrict, mealtime, tags, service, and price. Logic is AND across dimensions, OR within a dimension. Both the list and the map markers update.
- Near me: a geolocation toggle that sorts the list by distance and shows each spot's distance.
- Map tiles use Leaflet + OpenStreetMap (no API key). The CDN dependency is centralized in
assets.js— no hardcoded URLs in the HTML. - External link/search URLs (Google Maps, web/YouTube search) and the map-tile URL live in
links.js(FoodieLinks), keeping rawhttpsendpoints out of the page markup.
A 中文 / EN toggle (top-right) switches all UI text and data between en and zh. The choice persists in localStorage under foodie.lang and is shared across pages. UI strings live in i18n.json.
All content is JSON loaded at runtime via api.js (FoodieApi).
One file per eatery, listed by id in data/manifest.json (regenerated by tools/build-manifest.mjs); the file name is eatery-<id>.json. Add an eatery by dropping its file in data/ and re-running that script. Top-level fields are language-neutral; en / zh blocks hold localized text.
| Field | Scope | Notes |
|---|---|---|
id, lat, lng, emoji |
top | identity + map marker |
hasBranches |
top | boolean — has other branches |
price |
top | $ … $$$$ |
partySize |
top | { "min", "max" } (max: null = open-ended) |
website |
top | optional — array of { "platform", "url" } links (platform: official, facebook, instagram, …); each is shown on menu.html. A bare URL string is treated as official. |
reservationUrl |
top | optional — array of { "platform", "url" } booking links. One usable link → Reserve opens it directly; several → each platform is shown as an option; none/absent → no Reserve link shown. A bare URL string also works. |
hidden |
top | optional — true excludes it from the list/map |
name, cuisine, city, district, address, note |
en/zh |
text |
shoppingDistrict, mealtime, tags, service |
en/zh |
arrays |
menu |
en/zh |
optional — array of { "name", "price", "note"? } shown on menu.html |
district is the administrative region (e.g. 萬華區); shoppingDistrict (商圈) is the colloquial commercial/dining district (e.g. 西門).
One file per recipe, registered by id in api.js (DISH_IDS). Add a dish by dropping its file and listing its id.
- Eatery addresses and coordinates were compiled by hand and are approximate — verify before relying on them.