Updated ReadMe file and added the html file for the project
This commit is contained in:
1 parent
03c10184e2
commit
ce6b01dacb
2 files changed
+327
-2
No files matched your search
@@ -1,3 +1,44 @@
|
||||
# Inlander-Restaurant-Week-Picker
|
||||
# Inlander Restaurant Week Picker
|
||||
|
||||
Claude Code built this web app for selecting a restaurant during the Inlander Restaurant Week
|
||||
A single-file web app for browsing, marking favorites, and randomly selecting a restaurant during [Inlander Restaurant Week](https://inlanderrestaurantweek.com/).
|
||||
|
||||
## Overview
|
||||
|
||||
The app presents all participating IRW restaurants in a two-panel layout — a scrollable list on the left and a detail view on the right — making it easy to browse full multi-course menus before deciding where to eat.
|
||||
|
||||
Each couple gets their own set of interest checkboxes ("His Picks" / "Her Picks") that persist in `localStorage` across sessions. Once both people have marked their favorites, the built-in randomizer picks a winner from the mutually-agreed pool.
|
||||
|
||||
## Features
|
||||
|
||||
- **Browse & search** — filter restaurants by name, price tier ($25 / $35 / $45), neighborhood/area, and cuisine type
|
||||
- **Full menu detail** — click any restaurant to view its complete multi-course IRW menu with descriptions
|
||||
- **Dual interest tracking** — independently mark restaurants as interesting for two people; selections are saved automatically in the browser
|
||||
- **Interest filter** — narrow the list to His Picks, Her Picks, Both Picked, or Either Picked
|
||||
- **Random picker** — "Pick for Us!" draws a random winner from the currently filtered pool, with an option to re-roll
|
||||
- **No dependencies** — pure HTML, CSS, and vanilla JavaScript; open the file directly in any browser
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open `restaurant-picker.html` in a web browser (no server required).
|
||||
2. Browse the restaurant list or use the toolbar filters to narrow choices.
|
||||
3. Check the **You** and **Wife** boxes on any restaurant you're each interested in.
|
||||
4. Click **Pick for Us!** to randomly select from the restaurants you both want.
|
||||
5. Re-roll as needed, or click **View Details** to jump to the winner's menu.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Details |
|
||||
|-------|---------|
|
||||
| Structure | HTML5 |
|
||||
| Styling | Inline CSS (dark red/gold IRW theme) |
|
||||
| Logic | Vanilla JavaScript |
|
||||
| Persistence | Browser `localStorage` |
|
||||
| Dependencies | None |
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
restaurant-picker.html # The entire app — HTML, CSS, and JS in one file
|
||||
LICENSE
|
||||
README.md
|
||||
```
|
||||
@@ -0,0 +1,284 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>IRW Restaurant Picker</title>
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Segoe UI',sans-serif;background:#1a0a00;color:#f0e6d3;min-height:100vh}
|
||||
header{background:#8b1a1a;padding:20px 30px;display:flex;align-items:center;gap:16px}
|
||||
header h1{font-size:1.5rem;font-weight:700;color:#f5c842}
|
||||
header p{font-size:0.85rem;color:#f0cca0;margin-top:3px}
|
||||
.toolbar{background:#2a1200;padding:12px 24px;display:flex;flex-wrap:wrap;gap:10px;align-items:center;border-bottom:1px solid #5a2a00}
|
||||
.toolbar input,.toolbar select{background:#3a1a00;border:1px solid #7a4a00;color:#f0e6d3;padding:7px 10px;border-radius:6px;font-size:0.85rem}
|
||||
.toolbar input{width:200px}
|
||||
.toolbar select{cursor:pointer}
|
||||
.btn{padding:7px 16px;border:none;border-radius:6px;cursor:pointer;font-size:0.85rem;font-weight:600;transition:all 0.2s}
|
||||
.btn-primary{background:#c0392b;color:white}
|
||||
.btn-primary:hover{background:#e74c3c}
|
||||
.btn-gold{background:#f5c842;color:#1a0a00}
|
||||
.btn-gold:hover{background:#f7d76c}
|
||||
.btn-outline{background:transparent;border:1px solid #7a4a00;color:#f0e6d3;text-decoration:none;display:inline-block}
|
||||
.btn-outline:hover{background:#3a1a00}
|
||||
.spacer{flex:1}
|
||||
.tag-counts{font-size:0.82rem;color:#c0906a}
|
||||
.main{display:flex;height:calc(100vh - 118px)}
|
||||
.list-panel{width:360px;min-width:280px;overflow-y:auto;border-right:1px solid #5a2a00;background:#1f0c00}
|
||||
.restaurant-card{padding:12px 14px;border-bottom:1px solid #3a1a00;display:flex;align-items:flex-start;gap:10px;cursor:pointer;transition:background 0.15s}
|
||||
.restaurant-card:hover{background:#2e1500}
|
||||
.restaurant-card.active{background:#4a2000;border-left:3px solid #f5c842;padding-left:11px}
|
||||
.card-checks{display:flex;flex-direction:column;gap:5px;padding-top:2px}
|
||||
.check-label{display:flex;align-items:center;gap:5px;cursor:pointer;font-size:0.72rem;white-space:nowrap}
|
||||
.check-label input{cursor:pointer;accent-color:#f5c842;width:13px;height:13px}
|
||||
.check-you{color:#7ec8e3}
|
||||
.check-wife{color:#f9a8d4}
|
||||
.card-info{flex:1;min-width:0}
|
||||
.card-name{font-weight:600;font-size:0.9rem;color:#f0e6d3;line-height:1.3}
|
||||
.card-meta{font-size:0.75rem;color:#a0785a;margin-top:2px}
|
||||
.price-badge{display:inline-block;padding:1px 6px;border-radius:10px;font-size:0.7rem;font-weight:700;margin-right:5px}
|
||||
.price-25{background:#1e5c2a;color:#7edd9a}
|
||||
.price-35{background:#5c4a0a;color:#f5c842}
|
||||
.price-45{background:#6b0f0f;color:#f9a8a8}
|
||||
.interest-dots{margin-top:4px;display:flex;gap:4px}
|
||||
.dot{width:7px;height:7px;border-radius:50%}
|
||||
.dot-you{background:#7ec8e3}
|
||||
.dot-wife{background:#f9a8d4}
|
||||
.detail-panel{flex:1;overflow-y:auto;padding:28px}
|
||||
.detail-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#5a3a20;text-align:center;gap:12px}
|
||||
.detail-empty .icon{font-size:3.5rem}
|
||||
.detail-header h2{font-size:1.7rem;color:#f5c842}
|
||||
.detail-header .meta{color:#a0785a;margin-top:6px;font-size:0.9rem}
|
||||
.detail-actions{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap}
|
||||
.detail-actions .check-label{font-size:0.88rem;background:#2a1200;padding:7px 12px;border-radius:8px;border:1px solid #5a2a00}
|
||||
.detail-actions .check-label input{width:15px;height:15px}
|
||||
.hours{background:#2a1200;padding:9px 14px;border-radius:8px;margin-bottom:18px;font-size:0.83rem;color:#c0906a}
|
||||
.course-section{margin-bottom:22px}
|
||||
.course-title{font-size:0.9rem;font-weight:700;color:#f5c842;text-transform:uppercase;letter-spacing:1px;border-bottom:1px solid #5a2a00;padding-bottom:7px;margin-bottom:12px}
|
||||
.menu-item{margin-bottom:12px}
|
||||
.menu-item-name{font-weight:600;color:#f0e6d3;font-size:0.92rem}
|
||||
.menu-item-desc{color:#a0785a;font-size:0.82rem;margin-top:3px;line-height:1.5}
|
||||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:100;display:flex;align-items:center;justify-content:center}
|
||||
.modal{background:#1f0c00;border:1px solid #7a4a00;border-radius:12px;padding:28px;max-width:460px;width:90%;text-align:center}
|
||||
.modal h2{color:#f5c842;font-size:1.3rem;margin-bottom:6px}
|
||||
.modal p{color:#a0785a;font-size:0.88rem;margin-bottom:20px}
|
||||
.winner-card{background:#2e1500;border:2px solid #f5c842;border-radius:10px;padding:18px;margin:14px 0}
|
||||
.winner-name{font-size:1.4rem;font-weight:700;color:#f5c842}
|
||||
.winner-meta{color:#c0906a;margin-top:5px}
|
||||
.winner-interest{margin-top:9px;display:flex;gap:8px;justify-content:center}
|
||||
.interest-pill{padding:3px 11px;border-radius:12px;font-size:0.78rem;font-weight:600}
|
||||
.pill-you{background:#1a3f50;color:#7ec8e3}
|
||||
.pill-wife{background:#4a1a2e;color:#f9a8d4}
|
||||
.modal-btns{display:flex;gap:10px;justify-content:center;margin-top:18px}
|
||||
.spin-anim{animation:pop 0.25s ease-out}
|
||||
@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}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div>
|
||||
<h1>🍽️ Inlander Restaurant Week Picker</h1>
|
||||
<p>Browse menus · Mark favorites · Let fate decide</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="toolbar">
|
||||
<input type="text" id="search" placeholder="Search restaurants..." oninput="applyFilters()"/>
|
||||
<select id="filterPrice" onchange="applyFilters()">
|
||||
<option value="">All Prices</option>
|
||||
<option value="25">$25</option><option value="35">$35</option><option value="45">$45</option>
|
||||
</select>
|
||||
<select id="filterArea" onchange="applyFilters()"><option value="">All Areas</option></select>
|
||||
<select id="filterCuisine" onchange="applyFilters()"><option value="">All Cuisines</option></select>
|
||||
<select id="filterInterest" onchange="applyFilters()">
|
||||
<option value="">All Restaurants</option>
|
||||
<option value="you">His Picks</option>
|
||||
<option value="wife">Her Picks</option>
|
||||
<option value="both">Both Picked</option>
|
||||
<option value="either">Either Picked</option>
|
||||
</select>
|
||||
<div class="spacer"></div>
|
||||
<span class="tag-counts" id="tagCounts"></span>
|
||||
<button class="btn btn-outline" onclick="clearAll()">Clear All</button>
|
||||
<button class="btn btn-gold" onclick="openPicker()">🎲 Pick for Us!</button>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="list-panel" id="listPanel"></div>
|
||||
<div class="detail-panel" id="detailPanel">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-overlay" id="pickerModal" style="display:none">
|
||||
<div class="modal">
|
||||
<h2>🎲 Tonight's Pick</h2>
|
||||
<p id="pickerSubtitle"></p>
|
||||
<div id="winnerDisplay"></div>
|
||||
<div class="modal-btns">
|
||||
<button class="btn btn-outline" onclick="closePicker()">Close</button>
|
||||
<button class="btn btn-primary" onclick="pickAgain()">Pick Again!</button>
|
||||
<button class="btn btn-gold" onclick="viewWinner()">View Menu</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const RESTAURANTS = [{"name":"315","slug":"315cuisine","price":45,"areas":["Coeur d'Alene"],"cuisine":"Eclectic","url":"https://inlanderrestaurantweek.com/project/315cuisine/","menu":{"hours":"Menu served Sun-Thu, 4-9 pm; Fri-Sat, 4-10 pm","phone":"(208) 667-9660","courses":{"First Course":[{"name":"Korean Meatballs","desc":"Tender savory meatballs glazed with lime hoisin and served with taekyung aioli and japchae noodles"},{"name":"Bang Bang Cauliflower","desc":"Buttermilk battered fried cauliflower tossed in sweet chili sauce, Asian slaw, pickled daikon, green onion GF V – Add prawn skewer $16"},{"name":"Crab Cakes","desc":"Golden seared snow crab meat, garlic lemon aioli, Creole remoulade, lemon supremes, orange fruit caviar – Add another crab cake $8"}],"Second Course":[{"name":"Grilled Pork Chop","desc":"Grilled bone-in pork chop, braised apples, garlic feta fingerlings, curtido slaw, pickled mustard seeds"},{"name":"Argentine Flank","desc":"Marinated steak, traditional chopped chimichurri, roasted fingerling corn hash, curtido slaw, serrano aioli – Add prawn skewer $16"},{"name":"Sicilian Black Cod","desc":"Wild-caught black cod, pan seared and served over orzo and a rustic Mediterranean sauce of tomatoes, kalamata olives and capers"}],"Third Course":[{"name":"Strawberry Shortcake Delight","desc":"Vanilla semifreddo swirled with strawberry coulis and shortcake bites"},{"name":"Brownie Pavé","desc":"Fudgy chocolate brownie stacked with toasted meringue, served with vanilla fleck semifreddo"},{"name":"Apple Crescendo","desc":"Handmade crescent roll layers filled with slow-cooked apples and velvety custard, baked to a golden finish and served with a silken apple brandy sauce"}]}}},{"name":"1898 Public House","slug":"1898","price":45,"areas":["North Spokane"],"cuisine":"American","url":"https://inlanderrestaurantweek.com/project/1898/","menu":{"hours":"Menu served daily, 4 pm-close","phone":"(509) 466-2121","courses":{"First Course":[{"name":"Ahi-Poke Tart","desc":"Marinated ahi poke, pickled cucumbers, avocado, mango, kewpie sriracha aioli, unagi sauce, seaweed flakes, savory tart shell"},{"name":"Antipasto Salad","desc":"Mixed greens, grilled artichokes, fresh mozzarella, basil, cherry tomatoes, Castelvetrano olives, pepperoncini, salami, golden Italian dressing"},{"name":"Steak Canapes","desc":"Fried polenta bites, sirloin bites, cremini mushrooms, gorgonzola cream sauce, balsamic drizzle"}],"Second Course":[{"name":"Top Sirloin","desc":"Char-broiled sirloin, pomme puree, roasted carrots, cabernet demi-glace, fried potato strings"},{"name":"Huli Huli Chicken","desc":"Marinated chicken thighs, jasmine rice, grilled pineapple, red peppers, green onion"},{"name":"Yaki Udon","desc":"Udon noodles, prawns, oyster sauce, broccoli, bok choy, red peppers, green onions, seaweed flakes"}],"Third Course":[{"name":"Tiramisu Chocolate Sphere","desc":"Espresso dipped lady fingers, sweet mascarpone cream, cocoa powder, chocolate sphere"},{"name":"Tres Leches","desc":"Sponge cake soaked in evaporated milk, condensed milk, and whole milk, topped with homemade whipped cream, toasted coconut flakes, strawberries"},{"name":"Crème Brulée","desc":"Classic French crème brulée, fresh mixed berries, whipped cream"}]}}},{"name":"1910 Restaurant & Lounge at Spokane Club","slug":"1910","price":35,"areas":["Downtown"],"cuisine":"Northwest","url":"https://inlanderrestaurantweek.com/project/1910/","menu":{"hours":"Menu served Mon-Sat, 4:30-9 pm; Closed Sun","phone":"(509) 459-4577","courses":{"First Course":[{"name":"Wild Mushroom Bisque","desc":"Classic wild mushroom bisque with brown butter roux with roasted garlic, finished with cream and sea salt – Add Salmon $8, Shrimp $10, Grilled Chicken $6"},{"name":"Garden Green Salad","desc":"Local Arcadian mixed greens with radish, alfalfa sprouts, English cucumber, and tomato with your choice of dressing GF V – Add Salmon $8, Shrimp $10, Grilled Chicken $6"},{"name":"Classic Caesar Salad","desc":"Classic Caesar salad with romaine hearts, fresh lemon, house-made garlic herb croutons, and Parmesan – Add Salmon $8, Shrimp $10, Grilled Chicken $6"}],"Second Course":[{"name":"1910 Braised Short Rib","desc":"12-hour red wine braised short rib with potato puree and roasted garlic green beans, finished with demi-glace. Pairs well with Townshend T3 Red – Bourbon Mushrooms $5, Shrimp Scampi $10, Crab Oscar $15"},{"name":"Columbia River Blackened Steelhead","desc":"Blackened steelhead served with Boursin risotto, braised carrots, and finished with lobster Nantua sauce. Pairs well with Airfield Sauvignon Blanc – Bourbon Mushrooms $5, Shrimp Scampi $10, Crab Oscar $15"},{"name":"Cast Iron Chicken","desc":"Chicken breast seared in a cast iron skillet with lemon thyme orzo, paired with butternut squash, and topped with pancetta hollandaise. Pairs well with Barnard Griffin Rosé of Sangiovese – Bourbon Mushrooms $5, Shrimp Scampi $10, Crab Oscar $15"}],"Third Course":[{"name":"HouLine truncated
|
||||
|
||||
let interests = JSON.parse(localStorage.getItem('irw_interests')||'{}');
|
||||
let filteredList = [];
|
||||
let activeSlug = null;
|
||||
let currentWinner = null;
|
||||
|
||||
// Build filter dropdowns
|
||||
(function(){
|
||||
const areas = new Set(), cuisines = new Set();
|
||||
RESTAURANTS.forEach(r => { r.areas.forEach(a=>areas.add(a)); cuisines.add(r.cuisine); });
|
||||
const asel = document.getElementById('filterArea');
|
||||
[...areas].sort().forEach(a => { const o=document.createElement('option'); o.value=o.textContent=a; asel.appendChild(o); });
|
||||
const csel = document.getElementById('filterCuisine');
|
||||
[...cuisines].sort().forEach(c => { const o=document.createElement('option'); o.value=o.textContent=c; csel.appendChild(o); });
|
||||
})();
|
||||
|
||||
function applyFilters(){
|
||||
const search = document.getElementById('search').value.toLowerCase();
|
||||
const price = document.getElementById('filterPrice').value;
|
||||
const area = document.getElementById('filterArea').value;
|
||||
const cuisine = document.getElementById('filterCuisine').value;
|
||||
const interest = document.getElementById('filterInterest').value;
|
||||
filteredList = RESTAURANTS.filter(r => {
|
||||
if(search && !r.name.toLowerCase().includes(search) && !r.cuisine.toLowerCase().includes(search)) return false;
|
||||
if(price && r.price !== parseInt(price)) return false;
|
||||
if(area && !r.areas.includes(area)) return false;
|
||||
if(cuisine && r.cuisine !== cuisine) return false;
|
||||
const ri = interests[r.slug]||{};
|
||||
if(interest==='you' && !ri.you) return false;
|
||||
if(interest==='wife' && !ri.wife) return false;
|
||||
if(interest==='both' && !(ri.you && ri.wife)) return false;
|
||||
if(interest==='either' && !(ri.you||ri.wife)) return false;
|
||||
return true;
|
||||
});
|
||||
renderList();
|
||||
updateCounts();
|
||||
}
|
||||
|
||||
function renderList(){
|
||||
const panel = document.getElementById('listPanel');
|
||||
if(!filteredList.length){ panel.innerHTML='<div class="no-results">No restaurants match your filters.</div>'; return; }
|
||||
panel.innerHTML = filteredList.map(r => {
|
||||
const ri = interests[r.slug]||{};
|
||||
const dots = [ri.you?'<span class="dot dot-you"></span>':'', ri.wife?'<span class="dot dot-wife"></span>':''].join('');
|
||||
return `<div class="restaurant-card${r.slug===activeSlug?' active':''}" data-slug="${r.slug}">
|
||||
<div class="card-checks">
|
||||
<label class="check-label check-you" onclick="event.stopPropagation()"><input type="checkbox" ${ri.you?'checked':''} onchange="toggleInterest('${r.slug}','you',this.checked)"/>His</label>
|
||||
<label class="check-label check-wife" onclick="event.stopPropagation()"><input type="checkbox" ${ri.wife?'checked':''} onchange="toggleInterest('${r.slug}','wife',this.checked)"/>Her</label>
|
||||
</div>
|
||||
<div class="card-info" onclick="showDetail('${r.slug}')">
|
||||
<div class="card-name">${r.name}</div>
|
||||
<div class="card-meta"><span class="price-badge price-${r.price}">$${r.price}</span>${r.cuisine} · ${r.areas.join(', ')}</div>
|
||||
${dots ? `<div class="interest-dots">${dots}</div>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function updateCounts(){
|
||||
let you=0,wife=0,both=0;
|
||||
Object.values(interests).forEach(v=>{ if(v.you)you++; if(v.wife)wife++; if(v.you&&v.wife)both++; });
|
||||
document.getElementById('tagCounts').textContent = `His: ${you} · Her: ${wife} · Both: ${both}`;
|
||||
}
|
||||
|
||||
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));
|
||||
renderList();
|
||||
updateCounts();
|
||||
if(activeSlug===slug) showDetail(slug);
|
||||
}
|
||||
|
||||
function clearAll(){
|
||||
if(!confirm('Clear all your selections?')) return;
|
||||
interests={};
|
||||
localStorage.setItem('irw_interests',JSON.stringify(interests));
|
||||
renderList(); updateCounts();
|
||||
}
|
||||
|
||||
function showDetail(slug){
|
||||
activeSlug=slug;
|
||||
renderList();
|
||||
const r = RESTAURANTS.find(r=>r.slug===slug);
|
||||
const ri = interests[slug]||{};
|
||||
if(!r) return;
|
||||
const m = r.menu||{};
|
||||
let coursesHTML = '';
|
||||
if(m.courses){
|
||||
['First Course','Second Course','Third Course'].forEach(course => {
|
||||
const items = m.courses[course];
|
||||
if(!items||!items.length) return;
|
||||
coursesHTML += `<div class="course-section"><div class="course-title">${course}</div>${
|
||||
items.map(item => `<div class="menu-item">
|
||||
<div class="menu-item-name">${item.name}</div>
|
||||
${item.desc?`<div class="menu-item-desc">${item.desc}</div>`:''}
|
||||
</div>`).join('')
|
||||
}</div>`;
|
||||
});
|
||||
}
|
||||
document.getElementById('detailPanel').innerHTML = `
|
||||
<div class="detail-header">
|
||||
<h2>${r.name}</h2>
|
||||
<div class="meta"><span class="price-badge price-${r.price}">$${r.price} per person</span>${r.cuisine} · ${r.areas.join(', ')}${m.phone?' · '+m.phone:''}</div>
|
||||
<div class="detail-actions">
|
||||
<label class="check-label check-you"><input type="checkbox" ${ri.you?'checked':''} onchange="toggleInterest('${slug}','you',this.checked)"/>He's interested</label>
|
||||
<label class="check-label check-wife"><input type="checkbox" ${ri.wife?'checked':''} onchange="toggleInterest('${slug}','wife',this.checked)"/>She's interested</label>
|
||||
<a href="${r.url}" target="_blank" class="btn btn-outline" style="font-size:0.82rem">View on Site ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
${m.hours?`<div class="hours">🕐 ${m.hours}</div>`:''}
|
||||
${coursesHTML||'<div style="color:#5a3a20;padding:20px 0">Menu details not available for this restaurant.</div>'}
|
||||
<div style="margin-top:28px;padding-top:18px;border-top:1px solid #3a1a00">
|
||||
<a href="${r.url}" target="_blank" style="color:#f5c842;font-size:0.88rem">View full menu on Inlander Restaurant Week ↗</a>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function getPool(){
|
||||
const both = RESTAURANTS.filter(r=>{ const ri=interests[r.slug]||{}; return ri.you&&ri.wife; });
|
||||
if(both.length) return both;
|
||||
return RESTAURANTS.filter(r=>{ const ri=interests[r.slug]||{}; return ri.you||ri.wife; });
|
||||
}
|
||||
|
||||
function openPicker(){
|
||||
const pool = getPool();
|
||||
if(!pool.length){ alert('No restaurants selected!\n\nCheck some boxes first, then try again.'); return; }
|
||||
document.getElementById('pickerModal').style.display='flex';
|
||||
pickAgain();
|
||||
}
|
||||
|
||||
function pickAgain(){
|
||||
const pool = getPool();
|
||||
const winner = pool[Math.floor(Math.random()*pool.length)];
|
||||
currentWinner = winner;
|
||||
const ri = interests[winner.slug]||{};
|
||||
const pills = [ri.you?'<span class="interest-pill pill-you">✓ His pick</span>':'', ri.wife?'<span class="interest-pill pill-wife">✓ Her pick</span>':''].filter(Boolean).join('');
|
||||
const bothCount = RESTAURANTS.filter(r=>{ const ri=interests[r.slug]||{}; return ri.you&&ri.wife; }).length;
|
||||
document.getElementById('pickerSubtitle').textContent = bothCount>0 ? `Picking from ${bothCount} restaurants you both liked!` : `Picking from ${pool.length} restaurants either of you liked`;
|
||||
document.getElementById('winnerDisplay').innerHTML = `<div class="winner-card spin-anim">
|
||||
<div class="winner-name">${winner.name}</div>
|
||||
<div class="winner-meta"><span class="price-badge price-${winner.price}">$${winner.price}</span>${winner.cuisine} · ${winner.areas.join(', ')}</div>
|
||||
<div class="winner-interest">${pills||'<span class="interest-pill" style="background:#3a2a00;color:#c0906a">Random selection</span>'}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function closePicker(){ document.getElementById('pickerModal').style.display='none'; }
|
||||
function viewWinner(){ closePicker(); if(currentWinner) showDetail(currentWinner.slug); }
|
||||
|
||||
// Init
|
||||
applyFilters();
|
||||
updateCounts();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user