Extracted the restaurant data from the HTML file and put it into a stand alone JSON file. Updated ReadMe with information on how to pull updated information for future events.

This commit is contained in:
derekc committed 2026-02-24 11:12:10 -08:00
1 parent 7e74184d37
commit 34b54867a0
3 files changed
+6820 -13

No files matched your search

File diff suppressed because it is too large. Load diff
+10 -7
View File
@@ -1,6 +1,6 @@
# Inlander Restaurant Week Picker # Inlander Restaurant Week Picker
A single-file web app for browsing, marking favorites, and randomly selecting a restaurant during [Inlander Restaurant Week](https://inlanderrestaurantweek.com/). A web app for browsing, marking favorites, and randomly selecting a restaurant during [Inlander Restaurant Week](https://inlanderrestaurantweek.com/).
## Overview ## Overview
@@ -19,11 +19,11 @@ Each couple gets their own set of interest checkboxes ("His Picks" / "Her Picks"
- **Clear Filters** — one-click button to reset all search and filter fields back to their defaults - **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 - **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 - **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; open the file directly in any browser - **No dependencies** — pure HTML, CSS, and vanilla JavaScript; requires a local web server (see Usage)
## Usage ## Usage
1. Open `restaurant-picker.html` in a web browser (no server required). 1. Serve the folder from a web server and open `restaurant-picker.html` in a browser. (The app fetches `2026-restaurants.json` at runtime, so it cannot be opened directly as a local `file://` URL — a server is required. A simple option is the VS Code Live Server extension, or `npx serve .` in the project folder.)
2. Browse the restaurant list or use the toolbar filters to narrow choices. 2. Browse the restaurant list or use the toolbar filters to narrow choices.
3. 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. 3. 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.
4. Check the **You** and **Wife** boxes on any restaurant you're each interested in. 4. Check the **You** and **Wife** boxes on any restaurant you're each interested in.
@@ -44,16 +44,19 @@ Each couple gets their own set of interest checkboxes ("His Picks" / "Her Picks"
## File Structure ## File Structure
``` ```
restaurant-picker.html # The entire app — HTML, CSS, and JS in one file 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 LICENSE
README.md 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 ## Data Entry Guide
Each year's restaurant data is stored as a JavaScript array (`const RESTAURANTS = [...]`) inline in `restaurant-picker.html`. The source data comes from the [IRW restaurant listing pages](https://inlanderrestaurantweek.com/restaurants/). This section covers the data schema and common pitfalls to avoid when entering or updating restaurant records. 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](https://inlanderrestaurantweek.com/restaurants/). This section covers the data schema and common pitfalls to avoid when entering or updating restaurant records.
### Restaurant Object Schema ### Restaurant Object Schema
@@ -151,6 +154,6 @@ Signs that an entry has this problem:
### Special Characters ### Special Characters
The data lives inside a JavaScript string, 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 JS string delimiters. Em dashes (`–`) and curly apostrophes (`'`) from the source website copy fine as-is. 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: field delimiters 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"`), JavaScript will fail to parse the entire `RESTAURANTS` array and no restaurants will appear in the app. Always verify that the structural quotes in the data use the straight ASCII `"` character (U+0022). **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).
+12 -6
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
@@ -171,7 +171,7 @@ header p{font-size:0.85rem;color:#f0cca0;margin-top:3px}
</div> </div>
</div> </div>
<script> <script>
const RESTAURANTS = [{"name":"315","slug":"315cuisine","price":45,"areas":["Coeur d'Alene"],"cuisine":"Eclectic","url":"https://inlanderrestaurantweek.com/project/315cuisine/","menu":{"hours":"Menu served Sun-Thu, 4-9 pm; Fri-Sat, 4-10 pm","phone":"(208) 667-9660","courses":{"First Course":[{"name":"Korean Meatballs","desc":"Tender savory meatballs glazed with lime hoisin and served with taekyung aioli and japchae noodles"},{"name":"Bang Bang Cauliflower","desc":"Buttermilk battered fried cauliflower tossed in sweet chili sauce, Asian slaw, pickled daikon, green onion GF V – Add prawn skewer $16"},{"name":"Crab Cakes","desc":"Golden seared snow crab meat, garlic lemon aioli, Creole remoulade, lemon supremes, orange fruit caviar – Add another crab cake $8"}],"Second Course":[{"name":"Grilled Pork Chop","desc":"Grilled bone-in pork chop, braised apples, garlic feta fingerlings, curtido slaw, pickled mustard seeds"},{"name":"Argentine Flank","desc":"Marinated steak, traditional chopped chimichurri, roasted fingerling corn hash, curtido slaw, serrano aioli – Add prawn skewer $16"},{"name":"Sicilian Black Cod","desc":"Wild-caught black cod, pan seared and served over orzo and a rustic Mediterranean sauce of tomatoes, kalamata olives and capers"}],"Third Course":[{"name":"Strawberry Shortcake Delight","desc":"Vanilla semifreddo swirled with strawberry coulis and shortcake bites"},{"name":"Brownie Pavé","desc":"Fudgy chocolate brownie stacked with toasted meringue, served with vanilla fleck semifreddo"},{"name":"Apple Crescendo","desc":"Handmade crescent roll layers filled with slow-cooked apples and velvety custard, baked to a golden finish and served with a silken apple brandy sauce"}]}}},{"name":"1898 Public House","slug":"1898","price":45,"areas":["North Spokane"],"cuisine":"American","url":"https://inlanderrestaurantweek.com/project/1898/","menu":{"hours":"Menu served daily, 4 pm-close","phone":"(509) 466-2121","courses":{"First Course":[{"name":"Ahi-Poke Tart","desc":"Marinated ahi poke, pickled cucumbers, avocado, mango, kewpie sriracha aioli, unagi sauce, seaweed flakes, savory tart shell"},{"name":"Antipasto Salad","desc":"Mixed greens, grilled artichokes, fresh mozzarella, basil, cherry tomatoes, Castelvetrano olives, pepperoncini, salami, golden Italian dressing"},{"name":"Steak Canapes","desc":"Fried polenta bites, sirloin bites, cremini mushrooms, gorgonzola cream sauce, balsamic drizzle"}],"Second Course":[{"name":"Top Sirloin","desc":"Char-broiled sirloin, pomme puree, roasted carrots, cabernet demi-glace, fried potato strings"},{"name":"Huli Huli Chicken","desc":"Marinated chicken thighs, jasmine rice, grilled pineapple, red peppers, green onion"},{"name":"Yaki Udon","desc":"Udon noodles, prawns, oyster sauce, broccoli, bok choy, red peppers, green onions, seaweed flakes"}],"Third Course":[{"name":"Tiramisu Chocolate Sphere","desc":"Espresso dipped lady fingers, sweet mascarpone cream, cocoa powder, chocolate sphere"},{"name":"Tres Leches","desc":"Sponge cake soaked in evaporated milk, condensed milk, and whole milk, topped with homemade whipped cream, toasted coconut flakes, strawberries"},{"name":"Crème Brulée","desc":"Classic French crème brulée, fresh mixed berries, whipped cream"}]}}},{"name":"1910 Restaurant & Lounge at Spokane Club","slug":"1910","price":35,"areas":["Downtown"],"cuisine":"Northwest","url":"https://inlanderrestaurantweek.com/project/1910/","menu":{"hours":"Menu served Mon-Sat, 4:30-9 pm; Closed Sun","phone":"(509) 459-4577","courses":{"First Course":[{"name":"Wild Mushroom Bisque","desc":"Classic wild mushroom bisque with brown butter roux with roasted garlic, finished with cream and sea salt – Add Salmon $8, Shrimp $10, Grilled Chicken $6"},{"name":"Garden Green Salad","desc":"Local Arcadian mixed greens with radish, alfalfa sprouts, English cucumber, and tomato with your choice of dressing GF V – Add Salmon $8, Shrimp $10, Grilled Chicken $6"},{"name":"Classic Caesar Salad","desc":"Classic Caesar salad with romaine hearts, fresh lemon, house-made garlic herb croutons, and Parmesan – Add Salmon $8, Shrimp $10, Grilled Chicken $6"}],"Second Course":[{"name":"1910 Braised Short Rib","desc":"12-hour red wine braised short rib with potato puree and roasted garlic green beans, finished with demi-glace. Pairs well with Townshend T3 Red – Bourbon Mushrooms $5, Shrimp Scampi $10, Crab Oscar $15"},{"name":"Columbia River Blackened Steelhead","desc":"Blackened steelhead served with Boursin risotto, braised carrots, and finished with lobster Nantua sauce. Pairs well with Airfield Sauvignon Blanc – Bourbon Mushrooms $5, Shrimp Scampi $10, Crab Oscar $15"},{"name":"Cast Iron Chicken","desc":"Chicken breast seared in a cast iron skillet with lemon thyme orzo, paired with butternut squash, and topped with pancetta hollandaise. Pairs well with Barnard Griffin Rosé of Sangiovese – Bourbon Mushrooms $5, Shrimp Scampi $10, Crab Oscar $15"}],"Third Course":[{"name":"HouLine truncated let RESTAURANTS = [];
let interests = JSON.parse(localStorage.getItem('irw_interests')||'{}'); let interests = JSON.parse(localStorage.getItem('irw_interests')||'{}');
let filteredList = []; let filteredList = [];
@@ -179,14 +179,14 @@ let activeSlug = null;
let currentWinner = null; let currentWinner = null;
// Build filter dropdowns // Build filter dropdowns
(function(){ function initDropdowns(){
const areas = new Set(), cuisines = new Set(); const areas = new Set(), cuisines = new Set();
RESTAURANTS.forEach(r => { r.areas.forEach(a=>areas.add(a)); cuisines.add(r.cuisine); }); RESTAURANTS.forEach(r => { r.areas.forEach(a=>areas.add(a)); cuisines.add(r.cuisine); });
const asel = document.getElementById('filterArea'); const asel = document.getElementById('filterArea');
[...areas].sort().forEach(a => { const o=document.createElement('option'); o.value=o.textContent=a; asel.appendChild(o); }); [...areas].sort().forEach(a => { const o=document.createElement('option'); o.value=o.textContent=a; asel.appendChild(o); });
const csel = document.getElementById('filterCuisine'); const csel = document.getElementById('filterCuisine');
[...cuisines].sort().forEach(c => { const o=document.createElement('option'); o.value=o.textContent=c; csel.appendChild(o); }); [...cuisines].sort().forEach(c => { const o=document.createElement('option'); o.value=o.textContent=c; csel.appendChild(o); });
})(); }
function applyFilters(){ function applyFilters(){
const search = document.getElementById('search').value.toLowerCase(); const search = document.getElementById('search').value.toLowerCase();
@@ -355,8 +355,14 @@ function closeCourseBrowse(){ document.getElementById('courseBrowseOverlay').sty
function selectCourseDish(slug){ closeCourseBrowse(); showDetail(slug); } function selectCourseDish(slug){ closeCourseBrowse(); showDetail(slug); }
// Init // Init
applyFilters(); fetch('2026-restaurants.json')
updateCounts(); .then(function(r){ return r.json(); })
.then(function(data){
RESTAURANTS = data;
initDropdowns();
applyFilters();
updateCounts();
});
</script> </script>
<div class="course-modal-overlay" id="courseBrowseOverlay" style="display:none" onclick="if(event.target===this)closeCourseBrowse()"> <div class="course-modal-overlay" id="courseBrowseOverlay" style="display:none" onclick="if(event.target===this)closeCourseBrowse()">
<div class="course-modal"> <div class="course-modal">