Inlander Restaurant Week Picker
A web app for browsing, marking favorites, and randomly selecting a restaurant during Inlander Restaurant Week.
Overview
The app presents all participating IRW restaurants in a two-panel layout — a scrollable list on the left and a detail view on the right — making it easy to browse full multi-course menus before deciding where to eat.
Each couple gets their own set of interest checkboxes ("His Picks" / "Her Picks") that persist in localStorage across sessions. Once both people have marked their favorites, the built-in randomizer picks a winner from the mutually-agreed pool.
Features
- Browse & search — filter restaurants by name, price tier ($25 / $35 / $45), neighborhood/area, and cuisine type
- Full menu detail — click any restaurant to view its complete multi-course IRW menu with descriptions
- Dual interest tracking — independently mark restaurants as interesting for two people; selections are saved automatically in the browser
- Interest filter — narrow the list to His Picks, Her Picks, Both Picked, or Either Picked
- Random picker — "Pick for Us!" draws a random winner from the currently filtered pool, with an option to re-roll
- Course browse — dedicated First Course, Second Course, and Third Course buttons open a modal listing every dish for that course across all restaurants; click any dish to jump straight to that restaurant's detail view
- Clear Filters — one-click button to reset all search and filter fields back to their defaults
- Reset Selected — clears all saved picks (with a confirmation prompt) so you can start fresh
- Mobile-friendly — responsive single-panel layout on small screens; the list and detail views swap in place, with a Back button to return to the list
- No dependencies — pure HTML, CSS, and vanilla JavaScript; requires a local web server (see Usage)
Usage
- Serve the folder from a web server and open
restaurant-picker.htmlin a browser. (The app fetches2026-restaurants.jsonat runtime, so it cannot be opened directly as a localfile://URL — a server is required. A simple option is the VS Code Live Server extension, ornpx serve .in the project folder.) - Browse the restaurant list or use the toolbar filters to narrow choices.
- Use the First Course, Second Course, or Third Course buttons to browse all dishes for a given course and jump to any restaurant that interests you.
- Check the You and Wife boxes on any restaurant you're each interested in.
- Click Pick for Us! to randomly select from the restaurants you both want.
- Re-roll as needed, or click View Details to jump to the winner's menu.
- Use Clear Filters to reset the toolbar, or Reset Selected to clear all picks and start over.
Tech Stack
| Layer | Details |
|---|---|
| Structure | HTML5 |
| Styling | Inline CSS (dark red/gold IRW theme) |
| Logic | Vanilla JavaScript |
| Persistence | Browser localStorage |
| Dependencies | None |
File Structure
restaurant-picker.html # App shell — HTML, CSS, and JS logic
2026-restaurants.json # Restaurant data for the 2026 event (loaded at runtime via fetch)
LICENSE
README.md
Each year, create a new YYYY-restaurants.json and update the filename in the fetch() call near the bottom of restaurant-picker.html.
Data Entry Guide
Each year's restaurant data is stored as a JSON file (YYYY-restaurants.json) loaded by restaurant-picker.html at runtime. The source data comes from the IRW restaurant listing pages. This section covers the data schema and common pitfalls to avoid when entering or updating restaurant records.
Restaurant Object Schema
{
"name": "Restaurant Name",
"slug": "restaurantname",
"price": 35,
"areas": ["Downtown"],
"cuisine": "Italian",
"url": "https://inlanderrestaurantweek.com/project/restaurantname/",
"menu": {
"hours": "Menu served Tue-Sat, 5 pm-close",
"phone": "(509) 555-1234",
"courses": {
"First Course": [
{ "name": "Dish Name", "desc": "Ingredients and description GF" }
],
"Second Course": [
{ "name": "Dish Name", "desc": "Ingredients and description" }
],
"Third Course": [
{ "name": "Dish Name", "desc": "Ingredients and description V+" }
]
}
}
}
Field notes:
slug— lowercase, no spaces or special characters; used as an internal identifierprice— must be25,35, or45(the three IRW price tiers)areas— array; a restaurant can belong to more than one area (e.g.["Downtown", "Coeur d'Alene"])phone— use empty string""if not listed- Each course is an array — restaurants sometimes offer a choice of two or more dishes per course; add one object per option
Valid Area Values
Airway Heights Athol Coeur d'Alene Downtown
Hayden Liberty Lake North Spokane Post Falls
South Spokane Spokane Valley West Spokane Worley
ID (Coeur d'Alene-area catch-all)
Valid Cuisine Values
American Asian Barbecue Bistro Eclectic European French
Fusion Gastropub German Indian Irish Italian Latin
Mediterranean Mexican Middle Eastern Northwest Pizza
Seafood Southern Steakhouse Thai
New cuisine types can be added, but check existing values first to keep filters consistent.
Dietary Tags
Dietary tags go at the end of the desc field, separated by a space. Multiple tags are space-separated.
| Tag | Meaning |
|---|---|
GF |
Gluten Free |
GFA |
Gluten Free Available (on request or with substitution) |
V |
Vegetarian |
V+ |
Vegan |
DF |
Dairy Free |
Examples:
"desc": "Mixed greens, candied walnuts, goat cheese, balsamic vinaigrette GF V"
"desc": "House pasta with roasted vegetables – sub GF pasta available GFA V"
"desc": "Coconut curry with tofu and seasonal vegetables V+ GF DF"
Tags that are inline notes within a description (e.g. – sub GF crust for additional charge) are fine to leave mid-sentence; only the primary applicable tags belong at the end.
Common Data Entry Pitfall: Dietary Tag as Dish Name
The IRW website displays a dietary icon before the dish name. When copying menu data, it is easy to accidentally paste the dietary tag as the name field and run the dish name directly into the start of desc with no separator.
Wrong (broken):
{ "name": "GFA", "desc": "Margherita PizzaSan Marzano Tomato Sauce. Fresh Mozzarella." }
Correct:
{ "name": "Margherita Pizza", "desc": "San Marzano Tomato Sauce. Fresh Mozzarella. GFA" }
Signs that an entry has this problem:
- The
namefield is just a dietary tag ("GF","GFA","V+", etc.) - The
descfield starts with what looks like a dish name run together with the description (no space or punctuation between them, e.g."Chocolate TorteA rich slice of flourless paradise")
Special Characters
The data lives in a JSON file, so be careful with quotation marks inside descriptions. Use a curly/smart right double-quote (", U+201D) for inch marks (e.g. 7" pizza) rather than a straight ASCII ", which would break the JSON string. Em dashes (–) and curly apostrophes (') from the source website copy fine as-is.
Critical: JSON structural quotes must be straight ASCII double quotes. Some editors and AI tools auto-correct straight " to curly " / " (U+201C / U+201D). If curly quotes end up wrapping property names or values (e.g. "name" instead of "name"), the JSON file will fail to parse and no restaurants will appear in the app. Always verify that the structural quotes in the data use the straight ASCII " character (U+0022).
Incomplete Course Data (Missing or Too Few Menu Items)
Why It Happens
The Inlander Restaurant Week website is JavaScript-rendered — the actual menu content is loaded dynamically after the page shell is delivered. Automated tools (AI assistants, curl, simple web fetchers) only receive the bare HTML shell and never see the menu items. This causes courses to be entered with 0 or 1 item instead of the expected 3.
Signs a restaurant has this problem:
- A course array has 0 items — the key exists but the array is empty (
[]) - A course array has 1 item — only one option was captured instead of all three
- A course array has 2 items — one option was missed
How to Find Affected Restaurants
Run the following Python script against the JSON file to identify any restaurant where a course does not have exactly 3 items:
import json
with open('2026-restaurants.json', encoding='utf-8') as f:
restaurants = json.load(f)
for r in restaurants:
name = r.get('name', 'Unknown')
courses = r.get('menu', {}).get('courses', {})
issues = []
for course_name, items in courses.items():
if len(items) != 3:
issues.append(f"{course_name}: {len(items)} item(s)")
if issues:
print(f"{name}: {', '.join(issues)}")
Any restaurant printed by this script needs to be manually verified and corrected.
How to Manually Fix Incomplete Entries
- Open the restaurant's IRW page in a browser (the
urlfield in the JSON has the direct link, e.g.https://inlanderrestaurantweek.com/project/flyinggoat/). - Let the page fully load — the menu content is rendered by JavaScript and will not appear until the page has finished loading.
- For each course, confirm there are 3 options and copy all dish names and descriptions.
- Update the corresponding entry in
YYYY-restaurants.json, adding the missing dish objects to the course array. Each dish follows this format:{ "name": "Dish Name", "desc": "Full description here GFA" } - Re-run the Python script above to confirm no courses are still flagged.
Note: Some restaurants may genuinely offer fewer than 3 options for a course. If the live IRW page confirms only 1 or 2 choices are available, that count is correct and the entry does not need further changes.