Compare commits

...

14 Commits

7 changed files with 7917 additions and 136 deletions

6919
2025-restaurants.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
[
{
"eventDates": "FEBRUARY 26 MARCH 7, 2026",
"restaurants": [
{
"name": "315",
"slug": "315cuisine",
@@ -2230,18 +2232,42 @@
{
"name": "Albi Italian",
"desc": "Mixed greens, cucumber, red onion, croutons, pepperoncini, tomato, Italian vinaigrette GFA"
},
{
"name": "“A” Street Chips",
"desc": "deep fried thin sliced potatoes; choice of Flying Goat seasoning or salt & vinegar DF add cup of creamy gorgonzola dressing $2.25"
},
{
"name": "German Sausage Plate",
"desc": "German sausage, sauerkraut, potato wedges, house mustard GFA"
}
],
"Second Course": [
{
"name": "Driscoll",
"desc": "Roasted red pepper sauce, goat cheese, roasted eggplant, red onions, roasted cremini mushrooms, with truffle oil tossed arugula \u0026 shredded pecorino (7” pizza) sub GF crust for additional charge GFA"
}
"name": "Alberta",
"desc": "red sauce, fresh mozzarella, pepperoni, red onion, roasted basil (7” pizza) GFA sub GF crust for additional charge"
},
{
"name": "Short Rib Pizza",
"desc": "Extra virgin olive oil, short ribs, marinated portabella mushrooms, goat cheese, roasted garlic, green onion (7” pizza) GFA sub GF crust for additional charge"
},
{
"name": "Driscoll",
"desc": "roasted red pepper sauce, goat cheese, roasted eggplant, red onions, roasted cremini mushrooms, with truffle oil tossed arugula & shredded pecorino (7” pizza) GFA V sub GF crust for additional charge"
}
],
"Third Course": [
{
"name": "Affogato",
"desc": "Espresso, house-made biscotti \u0026 ice cream GFA"
"name": "Glazed Cinnamon Roll",
"desc": "house-made cinnamon roll"
},
{
"name": "“Affogato",
"desc": "espresso, house-made biscotti & ice cream GFA V"
},
{
"name": "Root Beer Float",
"desc": "Pelican root beer, vanilla ice cream GF"
}
]
}
@@ -5058,13 +5084,43 @@
{
"name": "Repub Fries",
"desc": "Crispy Idaho fries tossed in togarashi, topped with aioli and chili sauce. Garnished with cilantro, pickled jalapeños, blue cheese, and garlic confit add candied bacon $3 GF"
},
{
"name": "Double Cooked Chicken Wings",
"desc": "Cured, slow-cooked, and then fried. Choose BBQ spice, salt and pepper, hot, buffalo, or honey butter"
},
{
"name": "Deviled Eggs",
"desc": "Beer mustard whipped yolk, chile sauce, seed mix, candied bacon, green onion"
}
],
"Second Course": [
{
"name": "Loco Moco",
"desc": "8 oz. grilled WA beef on a bed of jasmine rice, covered in house-made mushroom gravy, with a sunny-side-up egg, kimchi, avocado, and fresh cilantro"
},
{
"name": "Monte Cristo",
"desc": "Crispy bacon, melted cheddar cheese and stone-ground dijon served on French toast with a side of house-made raspberry jam and choice of side add a fried egg $2"
},
{
"name": "Macaroni and Cheese",
"desc": "Curly noodles tossed in a creamy garlic-cheese sauce then baked until golden brown make it dirty $4"
}
],
"Third Course": [
{
"name": "Brown Butter Old Fashioned Donut",
"desc": "Classic glazed donut, cooked in brown butter, served warm, then topped with vanilla bean ice cream and sea salt"
},
{
"name": "NY Style Cheesecake",
"desc": "Whipped cream, caramel and candied pecans"
},
{
"name": "Churro Waffle",
"desc": "Tossed in cinnamon and sugar with salted caramel ice cream and fudge sauce"
}
]
}
}
@@ -5090,13 +5146,25 @@
{
"name": "Roasted Beet Salad",
"desc": "Balsamic roasted beets, goat cheese, arugula, candied pine nuts, sherry citrus vinaigrette GF"
},
{
"name": "Pi Bites",
"desc": "baked and crisp-fried pi dough, sea salt, Perry Street beer cheese sauce, green onion"
}
],
"Second Course": [
{
"name": "Wild Mushroom Pizza",
"desc": "Roasted pepper coulis, goat cheese, baby kale, caramelized shallots (7” pizza) sub GF crust for additional charge GFA"
}
"name": "Pork Carnitas Pizza",
"desc": "roasted tomatillo verde, cheese blend, jalapenos, red onion, chipotle crema (7” pizza) sub GF crust for additional charge GFA"
},
{
"name": "30th Off Grand",
"desc": "rossa sauce, Italian sausage, pepperoni, house cured ham, cheese blend (7” pizza) sub GF crust for additional charge GFA"
},
{
"name": "Wild Mushroom Pizza",
"desc": "roasted pepper coulis, goat cheese, baby kale, caramelized shallots (7” pizza) sub GF crust for additional charge GFA V"
}
],
"Third Course": [
{
@@ -5188,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"
}
]
}
@@ -5246,24 +5314,44 @@
"courses": {
"First Course": [
{
"name": "Fried CalamariDeep-fried calamari with a lemon garlic aioli",
"desc": "Zuppa ToscanaSpicy Italian sausage, kale and potatoes in a rich chicken broth Antipasto SaladSalami, pepperoni, Castelvetrano olives, mozzarella, roasted cherry tomatoes, romaine, red wine vinegar dressing"
"name": "Fried Calamari",
"desc": "Deep-fried calamari with a lemon garlic aioli DF"
},
{
"name": "Zuppa Toscana",
"desc": "Spicy Italian sausage, kale and potatoes in a rich chicken broth GF"
},
{
"name": "Antipasto Salad",
"desc": "Salami, pepperoni, Castelvetrano olives, mozzarella, roasted cherry tomatoes, romaine, red wine vinegar dressing GF"
}
],
"Second Course": [
{
"name": "Clam LinguineManila clams, linguine pasta, white wine clam sauce",
"desc": "Spicy Sausage Penne PastaGarlic, shallots, vodka-tomato cream sauce, parmesan, toasted bread, basil Grilled Chicken Bruschetta Sandwich6 oz. chicken breast, tomato-olive tapenade, sliced mozzarella, arugula, basil, roasted garlic puree on a ciabatta with tomato-basil chips A"
}
"name": "Clam Linguine",
"desc": "Manila clams, linguine pasta, white wine clam sauce DF"
},
{
"name": "Spicy Sausage Penne Pasta",
"desc": "Garlic, shallots, vodka-tomato cream sauce, parmesan, toasted bread, basil"
},
{
"name": "ZGrilled Chicken Bruschetta Sandwich",
"desc": "6 oz. chicken breast, tomato-olive tapenade, sliced mozzarella, arugula, basil, roasted garlic puree on a ciabatta with tomato-basil chips GFA"
}
],
"Third Course": [
{
"name": "Creme Brulée",
"desc": "House-made custard, caramelized candy shell, fresh berries GF"
"name": "Lemon Ricotta Cheesecake",
"desc": "Sweet ricotta, lemon, cookie crust, raspberry puree, candied lemon, whipped cream V"
},
{
"name": "GF V V+",
"desc": "SorbetChoice of seasonal offerings"
"name": "Creme Brulée",
"desc": "House-made custard, caramelized candy shell, fresh berries GF V"
},
{
"name": "Sorbet",
"desc": "Choice of seasonal offerings GF V V+ DF"
}
]
}
@@ -5286,18 +5374,42 @@
{
"name": "Fried Polenta",
"desc": "Fried polenta, topped with house-made marinara, mozzarella, balsmic glaze and fresh basil GF"
},
{
"name": "Salmon Cakes",
"desc": "House-made salmon cakes seasoned with peppers garlic, shallots, on a bed of arugula and shaved fennel topped with pineapple salsa DF"
},
{
"name": "Bacon Jam Crostini",
"desc": "Warmed brie cheese, topped with house-made onion bacon jam. Garnished with parsley and pickled jalapeños"
}
],
"Second Course": [
{
"name": "Beef and Mushroom Pappardelle",
"desc": "House-made braised short rib with mushrooms and pappardelle pasta in a cream sauce topped with parmesan and parsley Vegetarian option available GF"
}
"name": "Lemon Herbed Mahi",
"desc": "Seasoned mahi, on a bed of lemon herb rice, with roasted asparagus and blistered cherry tomatoes topped with caper lemon butter sauce GF"
},
{
"name": "Beef and Mushroom Pappardelle",
"desc": "House-made braised short rib with mushrooms and pappardelle pasta in a cream sauce topped with parmesan and parsley Vegetarian option available GF V"
},
{
"name": "Pork Belly and Grits",
"desc": "Pork belly over cheesy cheddar grits and roasted asparagus, topped with a house-made maple bourbon glaze and pickled jalapenos GF"
}
],
"Third Course": [
{
"name": "Blackberry CrispBlackberry crisp served with vanilla ice cream",
"desc": "Sweet PretzelsCinnamon sugar pretzels served with a cream cheese frosting Chocolate Peanut Butter PiePeanut butter cups sandwiched between layers of chocolate and peanut butter mousse on an Oreo crumb crust"
"name": "Blackberry Crisp",
"desc": "Blackberry crisp served with vanilla ice cream GF"
},
{
"name": "Sweet Pretzels",
"desc": "Cinnamon sugar pretzels served with a cream cheese frosting"
},
{
"name": "Chocolate Peanut Butter Pie",
"desc": "Peanut butter cups sandwiched between layers of chocolate and peanut butter mousse on an Oreo crumb crust"
}
]
}
@@ -5433,9 +5545,13 @@
"phone": "(208) 664-8008",
"courses": {
"First Course": [
{
"name": "House Caesar Salad",
"desc": "Crisp romaine lettuce tossed in house-made Caesar dressing. Topped with tomatoes, lemon and croutons GFA"
},
{
"name": "House Bistro Salad",
"desc": "Mixed arcadian greens tossed in your choice of our house-made dressings. Topped with grape tomatoes, mixed dried fruit and croutons GFA"
"desc": "Mixed arcadian greens tossed in your choice of our house-made dressings. Topped with grape tomatoes, mixed dried fruit and croutons GFA V"
},
{
"name": "Butternut Squash Bisque",
@@ -5443,12 +5559,31 @@
}
],
"Second Course": [
{
"name": "Peppercorn Sirloin",
"desc": "Pendleton Oregon sirloin steak served with house-made creamy peppercorn sauce, roasted garlic mashed potatoes, and seasonal vegetables GF"
},
{
"name": "Saffron Seafood Stew",
"desc": "Wild Norwegian salmon, Alaskan cod, shrimp, Andouille sausage, and red potatoes in a hearty house-made saffron tomato broth, with sourdough toast GFA"
},
{
"name": "Cilantro Lime Chicken",
"desc": "Two chile con limon seasoned chicken breasts served over house-made chimichurri rice and seasonal vegetables GF"
}
],
"Third Course": [
{
"name": "Huckleberry CheesecakeHuckleberry cheesecake served with huckleberry-gin compote",
"desc": "Death by Chocolate CakeDouble layered chocolate cake with a chocolate cream cheese frosting and finished with whipped cream Rotating Seasonal GelatoThree scoops of locally sourced gelato from Gelato by the Lake (Ask your server for the current selection)"
"name": "Huckleberry Cheesecake",
"desc": "Huckleberry cheesecake served with huckleberry-gin compote"
},
{
"name": "Death by Chocolate Cake",
"desc": "Double layered chocolate cake with a chocolate cream cheese frosting and finished with whipped cream"
},
{
"name": "Rotating Seasonal Gelato",
"desc": "Three scoops of locally sourced gelato from Gelato by the Lake (Ask your server for the current selection) GF"
}
]
}
@@ -5643,28 +5778,44 @@
"courses": {
"First Course": [
{
"name": "GF  V+",
"desc": "Fresh RollRice paper-wrapped lettuce, carrots, cabbage, cilantro, basil, cucumber and vermicelli rice noodles, served with peanut sauce and topped with special sauce"
"name": "Fish Cakes",
"desc": "Deep-fried blended tilapia with sliced string beans, mixed with our special curry. Served with sweet chili sauce and garnished with cucumber DF"
},
{
"name": "Fresh Roll",
"desc": "Rice paper-wrapped lettuce, carrots, cabbage, cilantro, basil, cucumber and vermicelli rice noodles, served with peanut sauce and topped with special sauce GF V+ DF "
},
{
"name": "Crab Rangoon",
"desc": "Deep fried wonton-wrapped crab meat, cream cheese, onion, celery, and carrots served with sweet chili sauce"
}
],
"Second Course": [
{
"name": "GF  V+",
"desc": "Panang Curry Braised Beef/Tofu“Braised beef, Panang curry, coconut milk, lime leaves and potatoes garnished with basil and bell pepper and kaffir lime leaves. Served with rice”"
}
"name": "Khao Man Gai",
"desc": "Slow-roasted chicken, Sirinyas special sauce, garlic, ginger, cilantro, white radish, green onion with jasmine rice and soup. Garnished with cucumber DF"
},
{
"name": "Panang Curry Braised Beef/Tofu",
"desc": "“Braised beef, Panang curry, coconut milk, lime leaves and potatoes garnished with basil and bell pepper and kaffir lime leaves. Served with rice” GF V+ DF"
},
{
"name": "Bahmi Haeng Hmudaeng",
"desc": "Lo mein noodles with roasted red pork and bok choy, topped with dried garlic, cilantro, green onion and special sauce"
}
],
"Third Course": [
{
"name": "GFA  V+",
"desc": "Bua LoiRice flour rolled sweet potato cooked in pandan and sweet coconut milk"
"name": "Bua Loi",
"desc": "Rice flour rolled sweet potato cooked in pandan and sweet coconut milk GFA V+ DF"
},
{
"name": "GFA  V+",
"desc": "Sirinyas Black Rice PuddingSweet black rice with coconut cream and pandan, topped with peaches"
"name": "Sirinyas Black Rice Pudding",
"desc": "Sweet black rice with coconut cream and pandan, topped with peaches GFA V+ DF"
},
{
"name": "Deep Fried Banana Ice Cream",
"desc": "Deep fried sweet banana wrapped with wheat flour, coconut ice cream topped with honey garnished with a cherry V+"
"desc": "Deep fried sweet banana wrapped with wheat flour, coconut ice cream topped with honey garnished with a cherry V+ DF"
}
]
}
@@ -5689,20 +5840,40 @@
"desc": "Pick two of our dips to sample with pita or veggies: Hummus, mutabel (roasted eggplant), muhamara (roasted red pepper and walnut), lebneh (mint-yogurt) Additional dips $4 GFA"
},
{
"name": "GF  V  V+",
"desc": "Falafel BitesSkewers scratch-made falafel served with our housemade taratour (sesame sauce) and pickled turnips Add hummus \u0026 pita $4"
"name": "Falafel Bites",
"desc": "Skewers scratch-made falafel served with our housemade taratour (sesame sauce) and pickled turnips GF V V+ DF Add hummus & pita $4"
},
{
"name": "Curry Cup",
"desc": "Our popular Armenian chicken curry, made with imported spices GFA Add rice pilaf $4 pita bread $1"
}
],
"Second Course": [
{
"name": "GFA  V  V+",
"desc": "DolmaPepper stuffed with a rice and walnut based mixture. Served with fattoush salad (pomegranate dressing) topped with pita chips and a Skewers sauce of choice"
}
"name": "Ghoozoo Eechee",
"desc": "Slow cooked Lamb shank served with lavash bread and a well-spiced rice, topped with an array of nuts. Comes with a Skewers sauce of choice GFA"
},
{
"name": "Hamem Chicken",
"desc": "Seasoned chicken quarter: bone-in, skin on, roasted. Served with pita and flavorful rice with onions and peppers. Comes with a Skewers sauce of choice GFA "
},
{
"name": "Dolma",
"desc": "Pepper stuffed with a rice and walnut based mixture. Served with fattoush salad (pomegranate dressing) topped with pita chips and a Skewers sauce of choice GFA V V+ DF"
}
],
"Third Course": [
{
"name": "GF V V+",
"desc": "Cardamom Rice PuddingCoconut-based rice pudding made with cardamom and rose. Topped with pistachios and rose petals"
"name": "Walnut Pakhlava",
"desc": "Pastry made with alternating thin layers of phyllo dough and walnuts. Sweetened with a house made floral-honey syrup (2 pieces) GFA Add 2 pieces $3"
},
{
"name": "GLayali Lubnan “Lebanese Nights”",
"desc": "Semolina pudding, topped with a layer of ashta (clotted cream), garnished with a heavy layer of pistachio, and sweetened with Skewers rose attar (syrup) GFA"
},
{
"name": "Cardamom Rice Pudding",
"desc": "Coconut-based rice pudding made with cardamom and rose. Topped with pistachios and rose petals GF V V+ DF"
}
]
}
@@ -5787,15 +5958,38 @@
{
"name": "Caprese Canapes",
"desc": "Rich compound cheese topped with seasoned tomatoes, basil, and balsamic reduction. Served with crostinis GFA"
},
{
"name": "Crab Cakes",
"desc": "House-made crab cakes served with house aioli"
}
],
"Second Course": [
{
"name": "Bánh Mì Burger",
"desc": "A juicy Angus beef patty cooked in a tangy marinade and topped with BBQ pork pâté, cilantro, pickled carrots and cucumber, all on a ciabatta bun GFA"
},
{
"name": "Birria Ramen",
"desc": "Rice noodles topped with birria consume, braised birria meat, pickled radish, shredded cabbage, onions, and cotija cheese GFA"
},
{
"name": "Vodka Pasta with Meatballs",
"desc": "Fettuccini noodles tossed in tomato vodka sauce with mushrooms, and red peppers. Served with house-made meatballs"
}
],
"Third Course": [
{
"name": "Lemon Tart",
"desc": "Bright lemon curd in a crisp crust V"
},
{
"name": "Apple Cheesecake Eggroll",
"desc": "House-made spiced apples and cheesecake wrapped in an eggroll shell and fried to perfection. Served with house-made bourbon caramel sauce V"
},
{
"name": "Chocolate Torte",
"desc": "A rich slice of flourless paradise GF"
"desc": "A rich slice of flourless paradise GF V"
}
]
}
@@ -5993,21 +6187,41 @@
"name": "Oeuf Mayonnaise",
"desc": "Eight-minute egg, scallion oil, flaky salt, fresh greens Add tobiko caviar $3; chili crisp $1.50 GF"
},
{
"name": "César Revisité",
"desc": "Romaine, creamy lemon and garlic dressing, bacon, fresh and crispy Parmesan cheese GF Add 8-minute Egg $2 Anchovies $2 Smoked Duck $8"
},
{
"name": "Bisque de Tomates",
"desc": "Tomato bisque….Rich, thick, luxurious, creamy, tangy, addictive… and CdA famous GF"
"desc": "Tomato bisque….Rich, thick, luxurious, creamy, tangy, addictive… and CdA famous GF V"
}
],
"Second Course": [
{
"name": "Salade de Betteraves Rôties et Burrata",
"desc": "Roasted beets, burrata cheese, scallion oil, arugula and flaky salt, served with Back Pocket Bakery baguette GFA"
}
"name": "Chicken Fricassée",
"desc": "A French country classic: Chicken thigh and mushrooms braised with white wine, chicken stock, shallots, cream, and herbs. Served with baby potatoes"
},
{
"name": "Croque Monsieur",
"desc": "Back Pocket Bakery baguette, Béchamel sauce, dijon, French ham and Swiss cheese baked until warm and melty. Served with mixed greens Sub tomato bisque ($2) or potato chips"
},
{
"name": "Salade de Betteraves Rôties et Burrata",
"desc": "Roasted beets, burrata cheese, scallion oil, arugula and flaky salt, served with Back Pocket Bakery baguette GFA V"
}
],
"Third Course": [
{
"name": "Pots de Crème",
"desc": "Rich chocolate custard finished with whipped cream Add 3 oz. of 2018 Duorum LBV Port $12 GF"
},
{
"name": "Pouding de Croissants au Caramel et aux Pistaches",
"desc": "Croissants baked in vanilla custard and served warm with caramel sauce, crushed pistachios and whipped cream Add 3 oz. Rare Wine Co. Boston Bual Madeira $12"
},
{
"name": "Fromage Comté",
"desc": "Comté is a French alpine cheese from the Jura, aged 18 months for nutty, savory depth and a smooth, firm texture. Served with pear jam & hazelnuts Add 3oz Huet Vouvray Moelleux 1ere Trie LHL $15"
}
]
}
@@ -6028,16 +6242,46 @@
"phone": "(208) 667-1170",
"courses": {
"First Course": [
{
"name": "Lumpia",
"desc": "Crispy Filipino spring rolls filled with savory pork, veggies, garlic, and spices, fried golden and served with a sweet chili dipping sauce"
},
{
"name": "Toasted Ravioli",
"desc": "Crispy, golden-breaded ravioli stuffed with portabella mushrooms and ricotta, fried to perfection and served with warm marinara for dipping V"
},
{
"name": "Smoked Steelhead Spread",
"desc": "Creamy smoked steelhead dip blended with herbs, lemon, and spices, rich and savory with a delicate smoky finish. Served with fried pita GFA"
}
],
"Second Course": [
{
"name": "Spam Fried Rice",
"desc": "Savory fried rice tossed with crispy Spam, scrambled eggs, green onions, and soy, finished with a hint of sesame for bold, comforting flavor GF"
}
"name": "Oxtail Stew",
"desc": "Slow-braised oxtail stew with fall-off-the-bone meat in a rich, savory broth of herbs, butter beans, and Jamaican spices served over rice GF"
},
{
"name": "Elote Mac and Cheese",
"desc": "Creamy mac and cheese blended with spiced chicken, roasted corn, chili, and cotija, inspired by classic elotes for a smoky, zesty, crave-worthy twist GFA"
},
{
"name": "Spam Fried Rice",
"desc": "Savory fried rice tossed with crispy Spam, scrambled eggs, green onions, and soy, finished with a hint of sesame for bold, comforting flavor GF DF"
}
],
"Third Course": [
{
"name": "Tiramisu",
"desc": "Classic Italian dessert with espresso-soaked ladyfingers layered with creamy mascarpone, lightly sweetened and finished with a dusting of cocoa GF"
},
{
"name": "Funfetti Edible Cookie Dough",
"desc": "Sweet, creamy edible cookie dough loaded with colorful sprinkles, white chocolate chips, rich vanilla flavor, and a fun, nostalgic crunch every bite"
},
{
"name": "Spanish Flan",
"desc": "Silky Spanish custard with rich vanilla flavor, baked to perfection and topped with a smooth, golden caramel sauce GF"
}
]
}
}
@@ -6175,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",
@@ -6207,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",
@@ -6232,21 +6476,45 @@
"phone": "(208) 292-5678",
"courses": {
"First Course": [
{
"name": "House-Made Focaccia",
"desc": "Honey Butter | Garlic Confit | Trapani Sea Salt V"
},
{
"name": "Crispy Prosciutto Wrapped Asparagus",
"desc": "Balsamic Reduction | Crushed Almonds GF"
"desc": "Balsamic Reduction | Crushed Almonds GF DF"
},
{
"name": "Romaine Salad",
"desc": "Marinated Anchovy | Lemon Garlic Aioli | Parmesan GF"
}
],
"Second Course": [
{
"name": "DFA",
"desc": "Duck ConfitRosemary Gnocchi | Broccolini | Butternut Squash | Fennel | Campari Glaze"
}
"name": "Seafood Linguine",
"desc": "Seafood Linguine | House-Made Pasta | Penn Cove Clams | Shrimp | Pacific Cod | Lemon Cream Sauce"
},
{
"name": "Duck Confit",
"desc": "Rosemary Gnocchi | Broccolini | Butternut Squash | Fennel | Campari Glaze DFA "
},
{
"name": "Royal Ranch Short Rib",
"desc": "Mushroom Risotto | Brussels Sprouts | Marsala Demi-Glace GF"
}
],
"Third Course": [
{
"name": "Tiramisu DomeSoft Sponge Cake | Zabaglione | Cocoa | Coffee Cream",
"desc": "Lemon Mascarpone CheesecakeStrawberries | Honey | Sweet Cream Pistachio CannoliCrisp Cannoli Shell | Sweet Ricotta | Toasted Pistachio | Dark Chocolate Curls"
"name": "Tiramisu Dome",
"desc": "Soft Sponge Cake | Zabaglione | Cocoa | Coffee Cream V"
},
{
"name": "Lemon Mascarpone Cheesecake",
"desc": "Strawberries | Honey | Sweet Cream V "
},
{
"name": "Pistachio Cannoli",
"desc": "Crisp Cannoli Shell | Sweet Ricotta | Toasted Pistachio | Dark Chocolate Curls V"
}
]
}
@@ -6268,12 +6536,31 @@
"courses": {
"First Course": [
{
"name": "Tomato Basil SoupA Tomato Street favorite! Served with fresh garlic bread",
"desc": "Minestrone SoupAn Italian classic, served with fresh garlic bread A House Garden or Caesar SaladHouse salad with your choice of dressing, or a Caesar salad. Served with fresh garlic bread A"
"name": "Tomato Basil Soup",
"desc": "A Tomato Street favorite! Served with fresh garlic bread V"
},
{
"name": "Minestrone Soup",
"desc": "An Italian classic, served with fresh garlic bread GFA"
},
{
"name": "House Garden or Caesar Salad",
"desc": "House salad with your choice of dressing, or a Caesar salad. Served with fresh garlic bread GFA"
}
],
"Second Course": [
{
"name": "Shrimp Aragosta",
"desc": "Fettuccini with a rich lobster cream sauce, sauteed prawns and bay shrimp, green onions and oven-dried tomatoes GFA"
},
{
"name": "Carbonara",
"desc": "A classic carbonara with a roasted garlic and black pepper cream sauce, tossed with bacon and spaghetti noodles, topped with parmesan cheese GFA"
},
{
"name": "Chicken Parmigiana",
"desc": "Breaded chicken breast topped with marinara sauce, melted mozzarella, provolone and parmesan cheeses, served with a side of fettuccine Alfredo"
}
],
"Third Course": [
{
@@ -6657,24 +6944,44 @@
"courses": {
"First Course": [
{
"name": "PolpetteHouse Made Meatballs. Nonna Sauce. Parmigiano-Reggiano.",
"desc": "Caesar SaladCrispy Capers. Parmigiano-Reggiano. Croutons. E OO. A FocacciaHouse Made. Tomato Sauce. Green Onions. Sea Salt. E OO."
"name": "Polpette",
"desc": "House Made Meatballs. Nonna Sauce. Parmigiano-Reggiano."
},
{
"name": "Caesar Salad",
"desc": "Crispy Capers. Parmigiano-Reggiano. Croutons. EVOO. GFA"
},
{
"name": "Focaccia",
"desc": "House Made. Tomato Sauce. Green Onions. Sea Salt. EVOO."
}
],
"Second Course": [
{
"name": "Margherita Pizza",
"desc": "San Marzano Tomato Sauce. Fresh Mozzarella. Basil. Basil Oil. Sub house gluten-free crust $4 GFA"
}
"name": "Calabrese Pizza",
"desc": "House Italian Sausage. Burrata. Calabrian Chili Sauce. Fresh Scallions. Sea Salt. GFA Sub house gluten-free crust $4"
},
{
"name": "Margherita Pizza",
"desc": "San Marzano Tomato Sauce. Fresh Mozzarella. Basil. Basil Oil. GFA V Sub house gluten-free crust $4"
},
{
"name": "Spaghetti & Meatballs",
"desc": "House Meatballs. Marinara. Fresh Basil. Parmigiano-Reggiano."
}
],
"Third Course": [
{
"name": "Zeppole",
"desc": "Italian Donut. Lemon. Mascarpone. V"
},
{
"name": "Chocolate Panna Cotta",
"desc": "Chantilly Cream. Shaved Chocolate. GF"
"desc": "Chantilly Cream. Shaved Chocolate. GF V"
},
{
"name": "Gelato",
"desc": "Whiskey Cream or Amarena Cherry. GF"
"desc": "Whiskey Cream or Amarena Cherry. GF V"
}
]
}
@@ -6695,20 +7002,44 @@
"courses": {
"First Course": [
{
"name": "Whipped Honey CornbreadOur Famous Cornbread With Whipped Honey Butter",
"desc": "Crawfish GougeresServed With House Creole Sauce Andouille Cheddar DipServed With Firecracker Crostini"
"name": "Whipped Honey Cornbread",
"desc": "Our Famous Cornbread With Whipped Honey Butter"
},
{
"name": "Crawfish Gougeres",
"desc": "Served With House Creole Sauce"
},
{
"name": "Andouille Cheddar Dip",
"desc": "Served With Firecracker Crostini"
}
],
"Second Course": [
{
"name": "Gator Red RedAlligator, Andouille Sausage, Red Beans, Holy Trinity",
"desc": "Bacon-Wrapped Shrimp And GritsWith Stone-Ground Cheesy Grits And Red Eye Gravy Short Rib A La QueenCarrots, Peas, Corn, Potato"
}
"name": "Gator Red Red",
"desc": "Alligator, Andouille Sausage, Red Beans, Holy Trinity GF"
},
{
"name": "Bacon-Wrapped Shrimp And Grits",
"desc": "With Stone-Ground Cheesy Grits And Red Eye Gravy GF"
},
{
"name": "Short Rib A La Queen",
"desc": "Carrots, Peas, Corn, Potato"
}
],
"Third Course": [
{
"name": "Praline BlondieThink Nutty, Caramel Goodness!",
"desc": "BeignetOur Classic French Doughnut! Have As Is, Make It An Affogato, Or A 21+ Affogato! Affogato $8, 21+ Affogato $9 Tarte Au CitronLemon Custard Tarte With Fresh Berries"
"name": "Praline Blondie",
"desc": "Think Nutty, Caramel Goodness! V"
},
{
"name": "Beignet",
"desc": "Our Classic French Doughnut! Have As Is, Make It An Affogato, Or A 21+ Affogato! Affogato $8, 21+ Affogato $9"
},
{
"name": "Tarte Au Citron",
"desc": "Lemon Custard Tarte With Fresh Berries V"
}
]
}
@@ -6728,29 +7059,45 @@
"phone": "(208) 758-7770",
"courses": {
"First Course": [
{
"name": "Stuffed Onion",
"desc": "Italian Sausage. Herbs. Parmigiano-Reggiano. GF"
},
{
"name": "Grilled Asparagus",
"desc": "Whipped Tarragon Chèvre. Fresh Peas. Lemon Jelly. GF"
"desc": "Whipped Tarragon Chèvre. Fresh Peas. Lemon Jelly. GF V"
},
{
"name": "Spanish Bomba",
"desc": "Sherry Braised Pork. Sauce Bravas. Garlic Aioli. GF"
"desc": "Sherry Braised Pork. Sauce Bravas. Garlic Aioli. GF DF"
}
],
"Second Course": [
{
"name": "Goose RavioliBrown Butter. Parmigiano-Reggiano.",
"desc": "Grilled Lamb SkewerZaatar. Charred Broccolini. Mint Yogurt. Migas al PastorPork Belly. Chorizo. Smoked Paprika. Red Peppers. Breadcrumbs. Grapes."
}
{
"name": "Goose Ravioli",
"desc": "Brown Butter. Parmigiano-Reggiano."
},
{
"name": "Grilled Lamb Skewer",
"desc": "Zaatar. Charred Broccolini. Mint Yogurt. GF"
},
{
"name": "Migas al Pastor",
"desc": "Pork Belly. Chorizo. Smoked Paprika. Red Peppers. Breadcrumbs. Grapes."
}
],
"Third Course": [
{
"name": "Galatopita",
"desc": "Lemon Custard Pie. Cinnamon. Honey. V"
},
{
"name": "Chocolate Crémeux",
"desc": "Chili Olive Oil. Flaky Sea Salt. GF"
"desc": "Chili Olive Oil. Flaky Sea Salt. GF V"
},
{
"name": "Cheese Plate",
"desc": "Tomme de Saison. Cherry Jam. House Cracker. GF"
"desc": "Tomme de Saison. Cherry Jam. House Cracker. GF V"
}
]
}
@@ -6770,29 +7117,49 @@
"phone": "(509) 838-4600",
"courses": {
"First Course": [
{
"name": "Jalapeño Cheddar Chicken Soup",
"desc": "Chicken stock base, jalapeños, sharp cheddar, shredded chicken, fresh herbs, and a touch of cream — a must-try! GF"
},
{
"name": "Mandarin Salad",
"desc": "Organic spring greens tossed in an Asian vinaigrette, topped with vegetable slaw, mandarins, toasted cashews and crispy wontons Vegan Available by request GFA"
"desc": "Organic spring greens tossed in an Asian vinaigrette, topped with vegetable slaw, mandarins, toasted cashews and crispy wontons GFA V Vegan Available by request"
},
{
"name": "Roasted Beet Salad",
"desc": "Organic spring greens tossed in a balsamic white truffle vinaigrette, topped with roasted beets, toasted hazelnuts and chèvre Vegan Available by request GF"
"desc": "Organic spring greens tossed in a balsamic white truffle vinaigrette, topped with roasted beets, toasted hazelnuts and chèvre GF V Vegan Available by request"
}
],
"Second Course": [
{
"name": "Lamb Ragu",
"desc": "Tender braised leg of lamb in a rich, rustic tomato sauce, tossed with bucatini pasta and finished with fine herbs and parmesan GFA"
},
{
"name": "Shrimp Risotto and Lobster Butter",
"desc": "Creamy risotto tossed with fresh asparagus, garlic-sauteed jumbo shrimp, and finished with a decadent lobster butter GF"
},
{
"name": "Yellow Curry Chicken or Tofu",
"desc": "Housemade yellow curry, choice of tofu or chicken. Served with a coconut rice cake, sweet peas, heirloom carrots, roasted red peppers, and pea shoots GF Vegan Available by request"
}
],
"Third Course": [
{
"name": "Mini Margarita Pie",
"desc": "Frozen key lime pie with graham cracker crust, silver tequila, whipped cream and red sea salt V"
},
{
"name": "Chocolate Pot de Crème",
"desc": "Rich mousse with coconut cream, chocolate and a hint of spice. Topped with raspberry purée and candied pecans Vegan Available by request GF"
"desc": "Rich mousse with coconut cream, chocolate and a hint of spice. Topped with raspberry purée and candied pecans GF V Vegan Available by request"
},
{
"name": "Wileys Bourbon Crème Brûlée",
"desc": "Brûléed custard with vanilla bean and orange peel. Topped with bourbon-nutmeg caramel and whipped cream on the side GF"
"desc": "Brûléed custard with vanilla bean and orange peel. Topped with bourbon-nutmeg caramel and whipped cream on the side GF V"
}
]
}
}
}
]
]
}

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')

48
memory/MEMORY.md Normal file
View File

@@ -0,0 +1,48 @@
# Inlander Restaurant Week Picker - Project Memory
## Quick Reference
- See `scraping-guide.md` for full year-scraping instructions and script templates
- See `html-structures.md` for HTML parsing patterns per restaurant type
- Project dir: `\\WinServ-20-3.chns.local\Profiles\derekc\Documents\Coding Projects\Gitea-CooperandGoodman-Inlander-Restaurant-Week-Picker\Inlander-Restaurant-Week-Picker`
## Key Constraints (CRITICAL)
- **WebFetch cannot access web.archive.org** — use `curl` via Bash tool instead
- **PowerShell cannot run scripts from UNC paths** (\\server\...) — always `cp` scripts to local temp first
- **bash `/tmp`** = `C:\Users\DEREKC~1.CHN\AppData\Local\Temp` (8.3 short name)
- **PowerShell temp** = `C:\Users\derekc.CHNSLocal\AppData\Local\Temp` (long name) — same dir, different string
- **Wayback Machine rate limits** to ~20 requests before throttling with 429; use 3-5 sec delays, wait 30+ min after getting blocked
## JSON Schema
Each entry in `YEAR-restaurants.json`:
```json
{
"name": "Restaurant Name",
"slug": "restaurantslug",
"price": 45,
"areas": ["Downtown"],
"cuisine": "American",
"url": "https://inlanderrestaurantweek.com/project/SLUG/",
"menu": {
"hours": "Menu served 5pm-close",
"phone": "(509) 555-1234",
"courses": {
"First Course": [{"name": "Dish Name", "desc": "Description"}],
"Second Course": [...],
"Third Course": [...]
}
}
}
```
Price is always 25, 35, or 45. gardenparty genuinely has 4 Third Course options.
## 2025 Data Status — COMPLETE
- **File**: `2025-restaurants.json` (121 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/3 — FIXED (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` — already run, tavolata is complete; kept for reference
- `check-2025.ps1` — validates all restaurant course counts

152
memory/html-structures.md Normal file
View File

@@ -0,0 +1,152 @@
# IRW Website HTML Structure Reference
## Restaurant Page URL
Live: `https://inlanderrestaurantweek.com/project/SLUG/`
Archived: `https://web.archive.org/web/TIMESTAMP/https://inlanderrestaurantweek.com/project/SLUG/`
## Page Framework
The site uses WordPress + Divi theme. Relevant container class: `et_pb_text_inner`.
Each course section typically occupies one or two `et_pb_text_inner` divs.
---
## Course Layout Types
### Layout A — Heading and items in SEPARATE divs (most restaurants)
```html
<div class="et_pb_text_inner"><h3>First Course</h3></div>
<div class="et_pb_text_inner">
<p><strong>Dish Name</strong><br/>Description</p>
<p><strong>Dish Name 2</strong><br/>Description 2</p>
</div>
<div class="et_pb_text_inner"><h3>Second Course</h3></div>
...
```
### Layout B — Heading and items in SAME div (tavolata, durkins, table13, others)
```html
<div class="et_pb_text_inner">
<h3>First Course</h3>
<p><strong>Dish Name</strong><br/>Description</p>
<p><strong>Dish Name 2</strong><br/>Description 2</p>
</div>
<div class="et_pb_text_inner">
<h3>Second Course</h3>
...
</div>
```
---
## Dish Name Tag Styles
### Style 1 — `<strong>` tag (most restaurants)
Examples: 315cuisine, anthonys, bardenay, barkrescuepub, etc.
```html
<p><strong>Dish Name</strong><br/>Description text here</p>
<p><strong>Dish Name</strong> <br/>With space before br</p>
```
### Style 2 — `<b>` tag with `<br/>` inside (India House, Lebanon, Karma, ponderosa)
```html
<p><b>Dish Name <br/></b><span>Description text</span></p>
<p><b>Dish Name<br/></b> Description without span</p>
```
Key: name is inside `<b>`, the `<br/>` is INSIDE the `<b>` tag.
### Style 3 — `<b>` + `<strong>` combo (1898 restaurant)
```html
<p><span><b>First Part</b></span><strong>Second Part</strong> Description</p>
```
Full dish name = "First Part" + " " + "Second Part"
---
## Field Extraction Patterns
### Name (from page title)
```
<title>Restaurant Name | Inlander Restaurant Week</title>
```
Regex: `<title>(.+?) \| Inlander`
### Price (WARNING: unreliable — use price listing page instead)
```html
<h1 style="text-align: left;"><strong>$45</strong></h1>
```
Regex: `<strong>\$(\d+)</strong>`
PROBLEM: Some pages show drink prices like $22 that match before the real price.
SOLUTION: Always build an authoritative slug→price map from the price listing page.
### Price Listing Page — Authoritative Prices
URL: `https://inlanderrestaurantweek.com/price/` (or Wayback archived version)
```html
<article class="et_pb_portfolio_item ... project_category_45 ...">
...
<a href="https://inlanderrestaurantweek.com/project/SLUG/">
```
Extract price tier from `project_category_(25|35|45)` CSS class.
Extract slug from `href=".../project/SLUG/"`.
### Cuisine
```html
CUISINE: AMERICAN COMFORT FOOD
```
Often inside `<strong>` or `<em>` tags. Extract uppercase text after "CUISINE:".
Apply `.ToTitleCase()` for proper formatting.
### Phone
Area codes: 509 (Spokane area) or 208 (Idaho/CDA area)
Pattern: `(509) 555-1234` or `(208) 555-1234`
Regex: `\((?:208|509)\) \d{3}-\d{4}`
### Hours
```
Menu served 5pm-9pm nightly
Menu served Thursday-Sunday, 5-9pm
```
Regex: `Menu served [^<]+`
### Area
Look for area keywords (ALL CAPS in source) anywhere in the HTML:
- DOWNTOWN, NORTH SPOKANE, SOUTH SPOKANE, WEST SPOKANE, SPOKANE VALLEY
- AIRWAY HEIGHTS, LIBERTY LAKE, COEUR D'ALENE, POST FALLS, HAYDEN, ATHOL, WORLEY
Default to ["Downtown"] if nothing matched.
Some restaurants appear in multiple areas — collect all matches.
---
## Dietary Tag Filtering
Skip these as dish names — they appear in `<strong>` but are dietary labels, not dish names:
- GF (gluten free)
- GFA (gluten free available)
- V, V+ (vegetarian, vegan)
- DF, DFA (dairy free, dairy free available)
- V:, V+A (legend lines)
- 2025 (year marker some restaurants include)
- Drink (some restaurants label beverage course)
Full regex: `^(GF|GFA|V\+?|DF|DFA|V:|2025|Drink|V\+A)$`
Also skip names matching `^[A-Z]{1,3}:` (legend lines like "GF: Gluten Free")
Also skip names shorter than 3 chars or longer than 80 chars.
---
## Restaurants by Known HTML Style (2025)
**Layout B (same-block)**: tavolata, durkins, table13, terraza, and others
**Style 2 (`<b>` tags)**: indiahouse, lebanon, karma, ponderosa, collectivekitchen, dryfly, masselowslounge, vieuxcarre, wileys, osprey, shawnodonnells, ganderryegrass
**Style 3 (`<b>`+`<strong>` combo)**: 1898
Note: These styles may change year to year as restaurants update their pages.
Always check a few representative pages before assuming the same structure applies.
---
## JS-Only Pages (no static HTML menu content)
These restaurants had no recoverable menu data from any Wayback snapshot in 2025:
heritage, kismet, littlenoodle, macdaddys, purgatory, redtail, republickitchen, republicpi, vicinopizza
Their pages are fully JS-rendered — the static HTML captured by Wayback Machine
shows the page shell but not the menu content. For future years, these may or may not
have static caches depending on server-side rendering changes.

237
memory/scraping-guide.md Normal file
View File

@@ -0,0 +1,237 @@
# IRW Scraping Guide — Full Process for Adding a New Year
## Overview
The Inlander Restaurant Week website (inlanderrestaurantweek.com) is WordPress/Divi.
Menu pages are partially JS-rendered but WP-Super-Cache creates static HTML snapshots
that the Wayback Machine archives. We scrape those static snapshots.
---
## Step 1: Find Restaurant Slugs
Fetch the price listing page to get all slugs for that year:
```bash
curl -s "https://web.archive.org/web/TIMESTAMP/https://inlanderrestaurantweek.com/price/" \
-o /tmp/irw-price-YEAR.html
```
Pick a timestamp close to the event (Wayback Machine format: YYYYMMDDHHmmss).
The price listing page has portfolio items like:
```html
<article class="et_pb_portfolio_item ... project_category_45">
<a href="https://inlanderrestaurantweek.com/project/SLUG/">
```
Extract slug from the href. The class `project_category_(25|35|45)` gives authoritative price.
**Important**: Scrape the price listing page FIRST and save the slug→price map.
Some restaurant pages have drink prices ($22, $33) that confuse the price parser.
---
## Step 2: Scrape Each Restaurant Page
Use a PowerShell script (written to project dir, copied to local temp to run):
**Wayback Machine URL format**:
```
https://web.archive.org/web/TIMESTAMP/https://inlanderrestaurantweek.com/project/SLUG/
```
**Key fields to extract**:
```powershell
# Name
$nameM = [regex]::Match($html, '<title>(.+?) \| Inlander')
# Price (from page, but USE PRICE LISTING MAP - this can be wrong)
$priceM = [regex]::Match($html, '<strong>\$(\d+)</strong>')
# Cuisine
$cuisineM = [regex]::Match($html, 'CUISINE:\s*([A-Z][A-Za-z/ ]+?)(?:\s*</|\s*<)')
$cuisine = (Get-Culture).TextInfo.ToTitleCase($c.ToLower())
# Phone
$phoneM = [regex]::Match($html, '\((?:208|509)\) \d{3}-\d{4}')
# Hours
$hoursM = [regex]::Match($html, 'Menu served [^<]+')
# Area (match against known area keys, case-insensitive)
$areaMap keys: "AIRWAY HEIGHTS","ATHOL","COEUR D'ALENE","POST FALLS","HAYDEN",
"LIBERTY LAKE","NORTH SPOKANE","SOUTH SPOKANE","SPOKANE VALLEY",
"WEST SPOKANE","WORLEY","DOWNTOWN"
```
**Rate limiting**: Add `Start-Sleep -Milliseconds 2000` between each request.
After a 429, stop and wait 30+ minutes before trying again.
---
## Step 3: Parse Menu Courses
### Course Block Extraction (`Get-CourseBlock`)
Two HTML layouts exist:
**Layout A** (most common): heading and items in SEPARATE `et_pb_text_inner` blocks
```powershell
# Strategy 1: find content between this label and next label
$m = [regex]::Match($html, [regex]::Escape($label) + '(.+?)(?=' + [regex]::Escape($nextLabel) + ')', $opts)
# Strategy 3 (fallback): items in next et_pb_text_inner block
$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)
```
**Layout B** (some restaurants — tavolata, durkins, table13, etc.): heading + items in SAME block
```powershell
# Strategy 2: extract <p> tags after </h3> within same div
$sameDivM = [regex]::Match($sub, '(?s)</h[123]>\s*(<p.+?)(?=</div>)', $opts)
```
### Dish Parsing (`Parse-Dish`)
Three tag styles exist:
**Style 1** (most restaurants): `<strong>` for name
```html
<p><strong>Dish Name</strong><br/>Description text</p>
```
**Style 2** (India House, Lebanon, Karma, others): `<b>` with `<br/>` before `</b>`
```html
<p><b>Dish Name <br/></b><span>Description text</span></p>
```
**Style 3** (1898): `<b>` + `<strong>` combination
```html
<p><span><b>Part1</b></span><strong>Part2</strong> Description</p>
```
**Multi-strategy parser** (handles all three):
```powershell
function Parse-Dish($pContent) {
$opts = [System.Text.RegularExpressions.RegexOptions]::Singleline
# Style 2: <b>Name <br/></b>
$bWithBrM = [regex]::Match($pContent, '(?s)<b>(.*?)<br\s*/?>', $opts)
if ($bWithBrM.Success) {
$name = Get-CleanText $bWithBrM.Groups[1].Value
if (Test-ValidDishName $name) {
$desc = Get-CleanText ($pContent.Substring($bWithBrM.Index + $bWithBrM.Length))
return [PSCustomObject]@{ name = $name; desc = $desc }
}
}
# Style 3: <b>Part1</b>...<strong>Part2</strong>
$bM = [regex]::Match($pContent, '(?s)<b>(.*?)</b>', $opts)
if ($bM.Success) {
$namePart = Get-CleanText $bM.Groups[1].Value
if (Test-ValidDishName $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 }
}
}
# Style 1: <strong>Name</strong>
$sM = [regex]::Match($pContent, '(?s)<strong>(.*?)</strong>', $opts)
if ($sM.Success) {
$name = Get-CleanText $sM.Groups[1].Value
if (-not (Test-ValidDishName $name)) { 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 Test-ValidDishName($name) {
$name.Length -ge 3 -and $name.Length -le 80 -and
$name -notmatch '^(GF|GFA|V\+?|DF|DFA|V:|2025|Drink|V\+A)$' -and
$name -notmatch '^[A-Z]{1,3}:'
}
function Test-DietaryTag($str) {
$str -match '^(GF|GFA|V\+?|DF|DFA|V:|2025|Drink|V\+A)$'
}
```
### HTML Cleanup
```powershell
function Get-CleanText($rawHtml) {
$t = $rawHtml -replace '<[^>]+>', ' '
$t = $t -replace '&amp;', '&' -replace '&#039;', "'" -replace '&quot;', '"'
$t = $t -replace '&lt;', '<' -replace '&gt;', '>' -replace '&nbsp;', ' '
$t = $t -replace '&#8211;', '-' -replace '&#8212;', '-'
($t -replace '\s+', ' ').Trim()
}
```
---
## Step 4: Fix Prices
After scraping, apply authoritative prices from the price listing page:
- Parse `project_category_(25|35|45)` CSS class from portfolio items
- Match slug from adjacent `href` attribute
- Build a hashtable and apply to all entries
Common gotcha: Restaurant pages may show $22 (wine), $33 (lunch) — these are NOT the event price.
---
## Step 5: Recover Missing Restaurants
If a restaurant has 0/0/0 courses:
1. Try alternate Wayback timestamps: `20250401000000`, `20250415000000`, `20250501000000`, `20250601000000`
2. Check if page uses Layout B (same-block) — add Strategy 2 to course block extractor
3. Check if page uses `<b>` tags instead of `<strong>` for dish names
**Known JS-only restaurants** (no static cache recoverable for 2025):
heritage, kismet, littlenoodle, macdaddys, purgatory, redtail, republickitchen, republicpi, vicinopizza
---
## Step 6: Output and Validation
```powershell
# Save as UTF-8 (important — special characters in restaurant names)
$json = $data | ConvertTo-Json -Depth 10
[System.IO.File]::WriteAllText($outPath, $json, [System.Text.Encoding]::UTF8)
# Validate: list any restaurant not at 3/3/3
$data | Where-Object {
$_.menu.courses.'First Course'.Count -ne 3 -or
$_.menu.courses.'Second Course'.Count -ne 3 -or
$_.menu.courses.'Third Course'.Count -ne 3
} | ForEach-Object {
"$($_.slug): $($_.menu.courses.'First Course'.Count)/$($_.menu.courses.'Second Course'.Count)/$($_.menu.courses.'Third Course'.Count)"
}
```
---
## PowerShell Script Execution Pattern (REQUIRED)
```bash
# Write script to project dir (via Write tool or Edit)
# Then in bash:
cp "//WinServ-20-3.chns.local/Profiles/derekc/Documents/Coding Projects/.../script.ps1" \
"/c/Users/derekc.CHNSLocal/AppData/Local/Temp/script.ps1"
powershell.exe -ExecutionPolicy Bypass -File "C:\Users\derekc.CHNSLocal\AppData\Local\Temp\script.ps1"
```
**Never** use `powershell -Command "..."` for multi-line scripts — escaping is unreliable.
**Never** try to run `.ps1` directly from `\\WinServ-20-3...` UNC path — execution policy blocks it.
---
## PowerShell Gotchas
- `"$slug: text"` fails if `:` follows var — use `"${slug}: text"`
- Function names like `Is-X`, `Decode-X`, `Parse-X` get PSScriptAnalyzer warnings (unapproved verbs) but work fine
- `return ,$array` (comma prefix) forces PowerShell to return an array, not unroll it
- `[System.IO.File]::WriteAllText(path, json, UTF8)` — use this, not `Out-File`, to avoid BOM/encoding issues

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">