Compare commits

..

11 Commits

6 changed files with 310 additions and 194 deletions

View File

@@ -1,4 +1,6 @@
[
{
"eventDates": "FEBRUARY 27 MARCH 8, 2025",
"restaurants": [
{
"name": "1898 Public House",
"slug": "1898",
@@ -2519,13 +2521,46 @@
"phone": "(509) 863-9235",
"courses": {
"First Course": [
{
"name": "Fried Pickles",
"desc": "Hand-breaded fried pickle spears served with a housemade honey mustard dressing V"
},
{
"name": "Panzanella Salad",
"desc": "A quick-pickled cucumber, red onion and tomato salad topped with our fresh-made seasoned croutons V V+ Add fresh mozzarella $2"
},
{
"name": "Pizza Bagels!",
"desc": "Mini bagels with house red sauce, fresh mozzarella and prosciutto. Baked to perfection and topped with fresh basil"
}
],
"Second Course": [
{
"name": "Impossible Meatball Sub",
"desc": "Impossible Italian meatballs smothered in a family recipe marinara. Topped with vegan mozzarella cheese and fresh basil on a sub roll “Anything is possible!” Kevin Garnett V V+"
},
{
"name": "Cajun Cheesesteak",
"desc": "Thin-sliced beef, house-blended Cajun seasoning, creole mayo, onions and peppers topped with melty cheese on a sub roll"
},
{
"name": "Muffins and Sauce",
"desc": "A King family favorite. Fresh-made mini biscuits in a slow roasted pork gravy"
}
],
"Third Course": [
{
"name": "Tap Beer",
"desc": "Choose from one of our eight local beers on tap V V+"
},
{
"name": "Deep Fried Banana",
"desc": "Battered and deep fried banana bites tossed in cinnamon and sugar. Served with a caramel dip V"
},
{
"name": "Heritage Ice Cream Cake",
"desc": "Locally made ice cream layered on a cookie crust V"
}
]
}
}
@@ -3125,13 +3160,46 @@
"phone": "(509) 309-2944",
"courses": {
"First Course": [
{
"name": "Grilled Queso",
"desc": "Grilled Chihuahua cheese, tostada, verde, fermented aioli, pico de gallo GF V "
},
{
"name": "Tacos Whettos",
"desc": "Beef cheek barbacoa, manchego cheese, fermented hot sauce, salsa, corn tortillas GF "
},
{
"name": "Panzanella Salad",
"desc": "Roasted squash, marinated tomatoes, smoked tomato vinaigrette, mixed greens, manchego cheese, toasted focaccia V "
}
],
"Second Course": [
{
"name": "Tamales",
"desc": "Choice of pork or vegetable tamale, pan fried, molé, bean ragout, machaca salsa GF V V+ "
},
{
"name": "Smoked Cheese Black Bean Empanada",
"desc": "Smoked black beans, house labneh, pickled chilies, verde, crema V "
},
{
"name": "Bocalo",
"desc": "Pan-seared white fish, harissa sauce, black garlic aioli, pickled vegetables, Spanish rice GF "
}
],
"Third Course": [
{
"name": "Tres Leches Cake",
"desc": "Milk-soaked cake, topped with housemade whipped cream and seasonal fruit V"
},
{
"name": "Mexican Chocolate Brownie",
"desc": "Mexican chocolate brownie, cardamom and coriander gelato, seasonal fruit V"
},
{
"name": "Sorbet",
"desc": "Seasonal fruit flavors GF V V+ "
}
]
}
}
@@ -3440,13 +3508,46 @@
"phone": "(509) 994-5157",
"courses": {
"First Course": [
{
"name": "Fresh Spring Rolls",
"desc": "Fresh veggies, rice noodles and fresh local basil wrapped in a rice paper, served with our house-made peanut sauce. Choice of shrimp or tofu GF GFA V V+"
},
{
"name": "Edamame",
"desc": "Whole soy bean pods seasoned with choice of sea salt, sriracha garlic or black garlic truffle Parmesan V V+"
},
{
"name": "Sake Clams",
"desc": "Clams sautéed in a lemongrass-basil compound butter and sake. Topped with local microgreens, fried garlic and a side of toasted crostini GFA"
}
],
"Second Course": [
{
"name": "Pho",
"desc": "Our full-bodied broth served with rice noodles, a cabbage-carrot blend, lime, jalapeno, onion and locally grown herbs. Choice of protein. Vegan options available GF GFA V V+ "
},
{
"name": "Pho-rrito",
"desc": "All our pho fixins and your choice of protein wrapped in a garlic herb tortilla and served with a side of our broth for dipping. Vegan options available GFA V V+ "
},
{
"name": "Spicy Curry Noodles",
"desc": "Thin rice noodles in Thai red curry broth with pickled veggies and our marinated onsen egg. Garnished with local microgreens and choice of protein GF GFA V V+ Add kimchi $3.50"
}
],
"Third Course": [
{
"name": "Matcha Panna Cotta",
"desc": "A creamy green tea panna cotta topped with raspberry sugar GF V"
},
{
"name": "Pineapple Sago",
"desc": "A sweet pineapple tapioca pudding, topped with a spicy pineapple chutney GF V"
},
{
"name": "Honey Paloma Green Tea Sorbet",
"desc": "Locally-made tea from Boba Proper that we turn into a refreshing sorbet GF V V+ Add a shot of sake $4"
}
]
}
}
@@ -3640,13 +3741,46 @@
"phone": "(509) 474-1336",
"courses": {
"First Course": [
{
"name": "Deep Fried Pickleback Pickles",
"desc": "Dill spears marinated in Jameson Whiskey, coated in beer batter and deep fried to a golden brown V "
},
{
"name": "Buffalo Chicken Mac Balls",
"desc": "Buffalo chicken mac and cheese, breaded and deep fried to a golden crunch"
},
{
"name": "Blue Cheese Wedge",
"desc": "Iceberg wedge salad topped with blue cheese dressing, blue cheese crumbles, cherry tomatoes, bacon crumbles and fresh chives GFA"
}
],
"Second Course": [
{
"name": "Cajun Mac and Cheese",
"desc": "Mac and cheese grilled with Andouille sausage, Cajun-seasoned chicken, blended with cheddar and pepper jack cheeses GFA Gluten-free noodles $3"
},
{
"name": "Beer Cheese Mac and Cheese",
"desc": "Mac and cheese grilled with German bratwurst, caramelized onions, blended with a beer cheese sauce and topped with crunchy pretzel bites GFA Gluten-free noodles $3"
},
{
"name": "MacDaddy Smash Burger",
"desc": "Two smashed beef patties topped with cheddar jack cheese, grilled red onion, jalapeño, and Cougar Gold mac and cheese, grilled to perfection. Served with fries GFA"
}
],
"Third Course": [
{
"name": "Blueberry Crumble",
"desc": "Sweet-tasting blueberry filling, delicious shortbread crust and topped with an old-fashioned coconut and oat crumble V "
},
{
"name": "Reeses Tart",
"desc": "Vegan tart shell filled with Reeses Peanut Butter and topped with a dark chocolate glaze V"
},
{
"name": "Huckleberry Ice Cream",
"desc": "House-made by our friends at the Milk Bottle V "
}
]
}
}
@@ -4539,13 +4673,46 @@
"phone": "(509) 290-6518",
"courses": {
"First Course": [
{
"name": "Roasted Goat Cheese with Blueberries and Honey",
"desc": "Roasted goat cheese with blueberries and honey, served with toasted baguette"
},
{
"name": "Pretzel Sticks",
"desc": "Pretzel sticks served with beer cheese or beer mustard. Add both beer cheese and beer mustard for $2"
},
{
"name": "Chicken Pot Pie Croquettes",
"desc": "Savory filling of chicken, onions, carrots and peas together with a rich bechamel sauce and encrusted in seasoned bread crumbs"
}
],
"Second Course": [
{
"name": "Grilled Chicken Sandwich",
"desc": "Marinated grilled chicken breast, jalapeños, lettuce, tomato, onion, sriracha aioli, jack cheese served on ciabatta"
},
{
"name": "Pork Belly Tacos",
"desc": "Flour tortillas, cabbage, green onion, pickled red onion, house Peruvian green sauce, sour cream and cilantro"
},
{
"name": "Whiskey Crusted Baby Back Ribs",
"desc": "Slow smoked baby back ribs, hand rubbed with our house seasoning, served with our house barbecue sauce on the side "
}
],
"Third Course": [
{
"name": "Bourbon Caramel Sauce Cheesecake",
"desc": "House-made bourbon caramel sauce served over New York-style cheesecake with toasted pecans"
},
{
"name": "Southern Whiskey Bread Pudding",
"desc": "Southern-style bread pudding with a whiskey caramel sauce and maple candied bacon topping with toasted pecans"
},
{
"name": "Lemon Blueberry Layered Cake",
"desc": "Sweet lemon blueberry layered cake with a cream cheese frosting"
}
]
}
}
@@ -5807,7 +5974,7 @@
"desc": "Lady Rose Apple, pink peppercorn, thyme, crostini V Add house-made focaccia $5"
},
{
"name": "Tavolàta",
"name": "Salad",
"desc": "Little gem lettuce, chicory, pistachio, red onion, Calabrian vinaigrette, pecorino GF V"
},
{
@@ -5825,12 +5992,23 @@
"desc": "Black pepper, butter, pecorino romano GFA"
},
{
"name": "Roasted",
"name": "Pork Loin",
"desc": "Fig mostarda, radicchio, hazelnut bread crumb GFA"
}
],
"Third Course": [
{
"name": "Zeppole",
"desc": "Lemon doughnuts, dark chocolate sauce V"
},
{
"name": "Tiramasu",
"desc": "Mascarpone, amaretto, espresso, lady finger V"
},
{
"name": "Sorbetto or Gelato",
"desc": "Salted shortbread cookie GFA V"
}
]
}
}
@@ -6737,4 +6915,5 @@
}
}
}
]
]
}

View File

@@ -1,4 +1,6 @@
[
{
"eventDates": "FEBRUARY 26 MARCH 7, 2026",
"restaurants": [
{
"name": "315",
"slug": "315cuisine",
@@ -5254,44 +5256,44 @@
"courses": {
"First Course": [
{
"name": "V  Nut Free",
"desc": "Cauliflower WingsBuffalo or Thai ginger style • carrot sticks • dipping sauce df Make it full size: $7"
"name": "Cauliflower Wings",
"desc": "Buffalo or Thai ginger style • carrot sticks • dipping sauce df V Nut Free Make it full size: $7"
},
{
"name": "GF  V+  DF  Nut Free",
"desc": "Blistered Shishitosmiso-tahini sauce • lemon salt • togarashi Make it full size: $7"
"name": "Blistered Shishitos",
"desc": "miso-tahini sauce • lemon salt • togarashi GF V+ DF Nut Free Make it full size: $7"
},
{
"name": "GFA  V+  DF  Nut Free  Soy Free",
"desc": "Creamy Tomato Soupbreadcrumbs • parsley • parmesan Make it a bowl $4"
"name": "Creamy Tomato Soup",
"desc": "breadcrumbs • parsley • parmesan GFA V+ DF Nut Free Soy Free Make it a bowl $4"
}
],
"Second Course": [
{
"name": "GFA  V+  DF  Nut Free",
"desc": "Bbq Jackfruit Sandwichrainbow slaw • crispy onion • Carolina style BBQ sauce • pretzel bun • choice of side"
"name": "Bbq Jackfruit Sandwich",
"desc": "rainbow slaw • crispy onion • Carolina style BBQ sauce • pretzel bun • choice of side GFA V+ DF Nut Free"
},
{
"name": "GFA  V+  DF  Nut Free",
"desc": "Lemongrass Yellow Currytofu • jasmine rice • roasted radish • snap pea • red bell pepper • carrot slaw • crispy shallot"
"name": "Lemongrass Yellow Curry",
"desc": "tofu • jasmine rice • roasted radish • snap pea • red bell pepper • carrot slaw • crispy shallot GFA V+ DF Nut Free"
},
{
"name": "Philly Sandwich",
"desc": "Braised mushroom medley • onion \u0026 peppers • smokey cashew provolone • long bread • choice of side V+"
"desc": "braised mushroom medley • onion & peppers • smokey cashew provolone • long bread • choice of side V+ DF"
}
],
"Third Course": [
{
"name": "GF  V+",
"desc": "Tea Timeearl grey cheesecake • lemon coulis • lemon-ginger sugar cookie"
"name": "Tea Time",
"desc": "earl grey cheesecake • lemon coulis • lemon-ginger sugar cookie GF V+ DF"
},
{
"name": "GF  V  DF  Nut Free",
"desc": "Cookies \u0026 Creamchocolate chunk cookie • vanilla ice cream • raspberry coulis"
"name": "Cookies & Cream",
"desc": "chocolate chunk cookie • vanilla ice cream • raspberry coulis GF V DF Nut Free"
},
{
"name": "GF  V+",
"desc": "Turtle Torteflourless chocolate torte • miso caramel • candied walnuts"
"name": "Turtle Torte",
"desc": "flourless chocolate torte • miso caramel • candied walnuts GF V+ DF"
}
]
}
@@ -6417,12 +6419,12 @@
"courses": {
"First Course": [
{
"name": "V DF",
"desc": "Crispy Spring RollsTasty combination of vegetables and Thai spices hand-rolled in a thin wrapper, deep fried to a golden brown and served with plum sauce"
"name": "Crispy Spring Rolls",
"desc": "Tasty combination of vegetables and Thai spices hand-rolled in a thin wrapper, deep fried to a golden brown and served with plum sauce V DF V+"
},
{
"name": "V V+",
"desc": "Golden SamosasA blend of sweet potatoes, carrots, peas, potatoes, cashews and Indian spices in a wonton wrapper, fried and served with a sweet chili dipping sauce"
"name": "Golden Samosas",
"desc": "A blend of sweet potatoes, carrots, peas, potatoes, cashews and Indian spices in a wonton wrapper, fried and served with a sweet chili dipping sauce V V+ DF"
},
{
"name": "Chicken Satay",
@@ -6449,8 +6451,8 @@
"desc": "Your choice of two scoops: vanilla, coconut, huckleberry or Thai tea GF"
},
{
"name": "GF V V+",
"desc": "Mango Sticky RiceWarm, sweet white sticky rice topped with mango and sweetened coconut cream"
"name": "Mango Sticky Rice",
"desc": "Warm, sweet white sticky rice topped with mango and sweetened coconut cream GF V V+ DF"
},
{
"name": "Coconut Cheesecake",
@@ -7159,4 +7161,5 @@
}
}
}
]
]
}

View File

@@ -16,6 +16,7 @@ Each couple gets their own set of interest checkboxes ("His Picks" / "Her Picks"
- **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
- **Year selector** — dropdown on the course browse bar switches between available event years (2025, 2026); restaurant data, filters, and interest picks are all scoped to the selected year
- **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
@@ -23,7 +24,7 @@ Each couple gets their own set of interest checkboxes ("His Picks" / "Her Picks"
## Usage
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.)
1. Serve the folder from a web server and open `restaurant-picker.html` in a browser. (The app fetches a `YYYY-restaurants.json` file 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.
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.
@@ -45,12 +46,25 @@ Each couple gets their own set of interest checkboxes ("His Picks" / "Her Picks"
```
restaurant-picker.html # App shell — HTML, CSS, and JS logic
2026-restaurants.json # Restaurant data for the 2026 event (loaded at runtime via fetch)
2026-restaurants.json # Restaurant + event date data for the 2026 event
2025-restaurants.json # Restaurant + event date data for the 2025 event
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`.
### Adding a New Year
1. Create a new `YYYY-restaurants.json` file following the schema described below (see [Data Entry Guide](#data-entry-guide)).
2. In `restaurant-picker.html`, add a new `<option>` to the year selector dropdown:
```html
<select id="yearSelect" onchange="loadYear(this.value)">
<option value="2027">2027</option> <!-- add new year at top -->
<option value="2026">2026</option>
<option value="2025">2025</option>
</select>
```
3. Update the hardcoded default in the `loadYear('2026')` init call at the bottom of the `<script>` block to the new year.
4. Update the static fallback date in the `<span id="eventDates">` header element — it will be overwritten at runtime, but the static value is shown briefly on first load.
---
@@ -58,6 +72,20 @@ Each year, create a new `YYYY-restaurants.json` and update the filename in the `
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.
### File-Level Schema
Each `YYYY-restaurants.json` is an **object** (not a bare array) with two top-level keys:
```json
{
"eventDates": "FEBRUARY 26 MARCH 7, 2026",
"restaurants": [ ... ]
}
```
- `eventDates` — the display string shown in the header when that year is selected; use an em dash (``) between the start and end dates
- `restaurants` — array of restaurant objects (see schema below)
### Restaurant Object Schema
```json
@@ -179,7 +207,9 @@ Run the following Python script against the JSON file to identify any restaurant
import json
with open('2026-restaurants.json', encoding='utf-8') as f:
restaurants = json.load(f)
data = json.load(f)
restaurants = data['restaurants']
for r in restaurants:
name = r.get('name', 'Unknown')

View File

@@ -1,123 +0,0 @@
# fix-tavolata.ps1
# Run this after the Wayback Machine rate limit resets (wait ~30 minutes after last run)
# Recovers tavolata's Third Course using the same-block parser strategy
$projectDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$jsonPath = Join-Path $projectDir '2025-restaurants.json'
$data = Get-Content $jsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
function Decode-Html($str) {
if (-not $str) { return $str }
($str -replace '&amp;','&' -replace '&#039;',"'" -replace '&quot;','"' -replace '&lt;','<' -replace '&gt;','>' -replace '&nbsp;',' ' -replace '\s+',' ').Trim()
}
function Get-CleanText($rawHtml) { Decode-Html ($rawHtml -replace '<[^>]+>', ' ') }
function Test-DietaryTag($str) { $str -match '^(GF|GFA|V\+?|DF|DFA|V:|2025|Drink|V\+A)$' }
function Get-Dish($pContent) {
$opts = [System.Text.RegularExpressions.RegexOptions]::Singleline
$bWithBrM = [regex]::Match($pContent, '(?s)<b>(.*?)<br\s*/?>', $opts)
if ($bWithBrM.Success) {
$name = Get-CleanText $bWithBrM.Groups[1].Value
if ($name.Length -ge 3 -and $name.Length -le 80 -and -not (Test-DietaryTag $name) -and $name -notmatch '^[A-Z]{1,3}:') {
return [PSCustomObject]@{ name = $name; desc = Get-CleanText ($pContent.Substring($bWithBrM.Index + $bWithBrM.Length)) }
}
}
$bM = [regex]::Match($pContent, '(?s)<b>(.*?)</b>', $opts)
if ($bM.Success) {
$namePart = Get-CleanText $bM.Groups[1].Value
if ($namePart.Length -ge 3 -and -not (Test-DietaryTag $namePart)) {
$afterB = $pContent.Substring($bM.Index + $bM.Length)
$sM2 = [regex]::Match($afterB, '(?s)^[^<]*<strong>(.*?)</strong>(.*)', $opts)
if ($sM2.Success) {
$p2 = Get-CleanText $sM2.Groups[1].Value
if (-not (Test-DietaryTag $p2) -and $p2.Length -ge 2) {
return [PSCustomObject]@{ name = "$namePart $p2".Trim(); desc = Get-CleanText $sM2.Groups[2].Value }
}
}
return [PSCustomObject]@{ name = $namePart; desc = Get-CleanText $afterB }
}
}
$sM = [regex]::Match($pContent, '(?s)<strong>(.*?)</strong>', $opts)
if ($sM.Success) {
$name = Get-CleanText $sM.Groups[1].Value
if ($name.Length -lt 3 -or $name.Length -gt 80 -or (Test-DietaryTag $name) -or $name -match '^[A-Z]{1,3}:') { return $null }
$afterBr = ''
if ($pContent -match '(?s)<br\s*/?>(.*?)$') { $afterBr = $matches[1] }
else { $am = [regex]::Match($pContent, '(?s)</strong>(.*?)$', $opts); if ($am.Success) { $afterBr = $am.Groups[1].Value } }
return [PSCustomObject]@{ name = $name; desc = Get-CleanText $afterBr }
}
return $null
}
function Get-Dishes($courseHtml) {
$dishes = [System.Collections.ArrayList]@()
$opts = [System.Text.RegularExpressions.RegexOptions]::Singleline
foreach ($pm in [regex]::Matches($courseHtml, '(?s)<p[^>]*>(.*?)</p>', $opts)) {
$pc = $pm.Groups[1].Value
if ($pc -notmatch '<b>|<strong>') { continue }
$d = Get-Dish $pc
if ($d -and $d.name) { $null = $dishes.Add($d) }
}
return ,$dishes
}
function Get-CourseBlock($html, $label, $nextLabel) {
$opts = [System.Text.RegularExpressions.RegexOptions]::Singleline
if ($nextLabel) {
$m = [regex]::Match($html, ([regex]::Escape($label) + '(.+?)(?=' + [regex]::Escape($nextLabel) + ')'), $opts)
if ($m.Success) { return $m.Groups[1].Value }
}
$idx = $html.IndexOf($label)
if ($idx -ge 0) {
$sub = $html.Substring($idx, [Math]::Min(8000, $html.Length - $idx))
$sameDivM = [regex]::Match($sub, '(?s)</h[123]>\s*(<p.+?)(?=</div>)', $opts)
if ($sameDivM.Success -and $sameDivM.Groups[1].Value -match '<p') { return $sameDivM.Groups[1].Value }
$im = [regex]::Match($sub, '(?s)et_pb_text_inner">(?!<h[123])(.+?)(?=et_pb_text_inner"><h|</div>\s*</div>\s*</div>\s*</div>\s*<div)', $opts)
if ($im.Success) { return $im.Groups[1].Value }
}
return ''
}
$r = $data | Where-Object { $_.slug -eq 'tavolata' }
Write-Host "tavolata currently: $($r.menu.courses.'First Course'.Count)/$($r.menu.courses.'Second Course'.Count)/$($r.menu.courses.'Third Course'.Count)"
$timestamps = @('20250306132630','20250401000000','20250415000000','20250501000000')
$success = $false
foreach ($ts in $timestamps) {
if ($success) { break }
Write-Host "Trying timestamp $ts..." -NoNewline
try {
$url = "https://web.archive.org/web/$ts/https://inlanderrestaurantweek.com/project/tavolata/"
$resp = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 60 -ErrorAction Stop
$html = $resp.Content
if ($html -match '429 Too Many') { throw "Rate limited" }
$first = Get-Dishes (Get-CourseBlock $html 'First Course' 'Second Course')
$second = Get-Dishes (Get-CourseBlock $html 'Second Course' 'Third Course')
$third = Get-Dishes (Get-CourseBlock $html 'Third Course' $null)
Write-Host " -> $($first.Count)/$($second.Count)/$($third.Count)"
if ($third.Count -gt 0) {
if ($first.Count -gt 0) { $r.menu.courses.'First Course' = @($first) }
if ($second.Count -gt 0) { $r.menu.courses.'Second Course' = @($second) }
$r.menu.courses.'Third Course' = @($third)
Write-Host "SUCCESS! tavolata Third Course recovered." -ForegroundColor Green
$success = $true
} else {
Write-Host " Third Course still empty, trying next timestamp..."
}
} catch {
Write-Host " ERROR: $_" -ForegroundColor Red
}
Start-Sleep -Seconds 10
}
if (-not $success) {
Write-Host "Could not recover tavolata Third Course. Try again later." -ForegroundColor Yellow
} else {
$json = $data | ConvertTo-Json -Depth 10
[System.IO.File]::WriteAllText($jsonPath, $json, [System.Text.Encoding]::UTF8)
Write-Host "Saved to $jsonPath"
}

View File

@@ -35,15 +35,14 @@ Each entry in `YEAR-restaurants.json`:
```
Price is always 25, 35, or 45. gardenparty genuinely has 4 Third Course options.
## 2025 Data Status
## 2025 Data Status — COMPLETE
- **File**: `2025-restaurants.json` (121 restaurants)
- **Wayback snapshot used**: `20250306132630` (primary), `20250401000000` (backup for some)
- **Complete (3/3/3+)**: 111 restaurants
- **Wayback snapshot used**: `20250306132630`
- **Complete (3/3/3+)**: 112 restaurants
- **gardenparty**: 3/3/4 — correct, it genuinely offers 4 dessert choices
- **tavolata**: 3/3/0needs fix-tavolata.ps1 run when rate limit resets
- **tavolata**: 3/3/3FIXED (recovered Third Course from snapshot `20250306132630`)
- **0/0/0 (JS-only, unrecoverable)**: heritage, kismet, littlenoodle, macdaddys, purgatory, redtail, republickitchen, republicpi, vicinopizza
## Scripts in Project Directory
- `fix-tavolata.ps1`run after rate limit resets to recover tavolata Third Course
- Copy to local temp and run: `cp ...\fix-tavolata.ps1 C:\Users\derekc.CHNSLocal\AppData\Local\Temp\`
- Then: `powershell.exe -ExecutionPolicy Bypass -File C:\Users\derekc.CHNSLocal\AppData\Local\Temp\fix-tavolata.ps1`
- `fix-tavolata.ps1`already run, tavolata is complete; kept for reference
- `check-2025.ps1` — validates all restaurant course counts

View File

@@ -74,7 +74,8 @@ header p{font-size:0.85rem;color:#f0cca0;margin-top:3px}
@keyframes pop{from{transform:scale(0.85);opacity:0}to{transform:scale(1);opacity:1}}
.no-results{padding:30px;text-align:center;color:#5a3a20}
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:#1a0a00}::-webkit-scrollbar-thumb{background:#5a2a00;border-radius:3px}
.course-browse-bar{background:#241000;padding:8px 24px;display:flex;gap:10px;border-bottom:1px solid #5a2a00}
.course-browse-bar{background:#241000;padding:8px 24px;display:flex;gap:10px;align-items:center;border-bottom:1px solid #5a2a00}
.course-browse-bar select{background:#3a1a00;border:1px solid #7a4a00;color:#f0e6d3;padding:7px 10px;border-radius:6px;font-size:0.85rem;cursor:pointer;margin-left:auto;flex-shrink:0}
.btn-course{background:#3a1a00;border:1px solid #7a4a00;color:#f5c842;padding:7px 20px;border-radius:20px;cursor:pointer;font-size:0.85rem;font-weight:600;transition:all 0.2s}
.btn-course:hover{background:#5a2a00;border-color:#f5c842}
.course-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.78);z-index:100;display:flex;align-items:center;justify-content:center}
@@ -97,8 +98,9 @@ header p{font-size:0.85rem;color:#f0cca0;margin-top:3px}
.toolbar select{flex:1;min-width:0}
.spacer{display:none}
.tag-counts{width:100%;order:10;text-align:center}
.course-browse-bar{padding:8px 12px}
.course-browse-bar{padding:8px 12px;flex-wrap:wrap}
.btn-course{flex:1;padding:7px 6px;text-align:center}
.course-browse-bar select{margin-left:0;width:100%}
.main{height:auto;min-height:calc(100svh - 98px);flex-direction:column}
.list-panel{width:100%;min-width:0;border-right:none;min-height:calc(100svh - 98px)}
.detail-panel{display:none;width:100%;padding:16px 14px}
@@ -120,7 +122,7 @@ header p{font-size:0.85rem;color:#f0cca0;margin-top:3px}
<h1>🍽️ Inlander Restaurant Week Picker</h1>
<p>Browse menus · Mark favorites · Let fate decide</p>
</div>
<span style="margin-left:auto;font-size:1.5rem;font-weight:700;color:#f5c842;white-space:nowrap">FEBRUARY 26 MARCH 7, 2026</span>
<span id="eventDates" style="margin-left:auto;font-size:1.5rem;font-weight:700;color:#f5c842;white-space:nowrap">FEBRUARY 26 MARCH 7, 2026</span>
</header>
<div class="toolbar">
<input type="text" id="search" placeholder="Search restaurants..." oninput="applyFilters()"/>
@@ -147,6 +149,10 @@ header p{font-size:0.85rem;color:#f0cca0;margin-top:3px}
<button class="btn btn-course" onclick="openCourseBrowse('First Course')">🥗 First Course</button>
<button class="btn btn-course" onclick="openCourseBrowse('Second Course')">🍖 Second Course</button>
<button class="btn btn-course" onclick="openCourseBrowse('Third Course')">🍮 Third Course</button>
<select id="yearSelect" onchange="loadYear(this.value)">
<option value="2026">2026</option>
<option value="2025">2025</option>
</select>
</div>
<div class="main">
<div class="list-panel" id="listPanel"></div>
@@ -172,8 +178,8 @@ header p{font-size:0.85rem;color:#f0cca0;margin-top:3px}
</div>
<script>
let RESTAURANTS = [];
let interests = JSON.parse(localStorage.getItem('irw_interests')||'{}');
let currentYear = '2026';
let interests = JSON.parse(localStorage.getItem('irw_interests_2026')||'{}');
let filteredList = [];
let activeSlug = null;
let currentWinner = null;
@@ -240,7 +246,7 @@ function toggleInterest(slug,who,checked){
if(!interests[slug]) interests[slug]={};
interests[slug][who]=checked;
if(!interests[slug].you && !interests[slug].wife) delete interests[slug];
localStorage.setItem('irw_interests',JSON.stringify(interests));
localStorage.setItem('irw_interests_'+currentYear,JSON.stringify(interests));
renderList();
updateCounts();
if(activeSlug===slug) showDetail(slug);
@@ -249,7 +255,7 @@ function toggleInterest(slug,who,checked){
function clearAll(){
if(!confirm('Clear all your selections?')) return;
interests={};
localStorage.setItem('irw_interests',JSON.stringify(interests));
localStorage.setItem('irw_interests_'+currentYear,JSON.stringify(interests));
renderList(); updateCounts();
}
function resetFilters(){
@@ -354,15 +360,37 @@ function openCourseBrowse(course){
function closeCourseBrowse(){ document.getElementById('courseBrowseOverlay').style.display = 'none'; }
function selectCourseDish(slug){ closeCourseBrowse(); showDetail(slug); }
function loadYear(year){
currentYear = year;
interests = JSON.parse(localStorage.getItem('irw_interests_'+year)||'{}');
activeSlug = null;
currentWinner = null;
document.querySelector('.main').classList.remove('show-detail');
document.getElementById('detailPanel').innerHTML = `
<div class="detail-empty">
<div class="icon">🍴</div>
<h3 style="color:#8b4a20">Select a restaurant to see its menu</h3>
<p>Check boxes to mark ones you're interested in, then hit "Pick for Us!"</p>
</div>`;
document.getElementById('filterArea').innerHTML = '<option value="">All Areas</option>';
document.getElementById('filterCuisine').innerHTML = '<option value="">All Cuisines</option>';
resetFilters();
fetch(year+'-restaurants.json', {cache:'no-store'})
.then(function(r){ return r.json(); })
.then(function(data){
RESTAURANTS = Array.isArray(data) ? data : data.restaurants;
if(data.eventDates) document.getElementById('eventDates').textContent = data.eventDates;
initDropdowns();
applyFilters();
updateCounts();
})
.catch(function(err){
document.getElementById('listPanel').innerHTML = '<div class="no-results">⚠️ Failed to load restaurant data.<br><small>'+err+'</small><br><small>Try serving this file from a local web server instead of opening it directly.</small></div>';
});
}
// Init
fetch('2026-restaurants.json')
.then(function(r){ return r.json(); })
.then(function(data){
RESTAURANTS = data;
initDropdowns();
applyFilters();
updateCounts();
});
loadYear('2026');
</script>
<div class="course-modal-overlay" id="courseBrowseOverlay" style="display:none" onclick="if(event.target===this)closeCourseBrowse()">
<div class="course-modal">