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": "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 \u0026 shredded pecorino (7” pizza) sub GF crust for additional charge GFA"
"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,12 +5146,24 @@
{
"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": "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"
"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": "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"
"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": "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"
"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": "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"
"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": "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. Sub house gluten-free crust $4 GFA"
"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); }
// Init
fetch('2026-restaurants.json')
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 = 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
loadYear('2026');
</script>
<div class="course-modal-overlay" id="courseBrowseOverlay" style="display:none" onclick="if(event.target===this)closeCourseBrowse()">
<div class="course-modal">