Updated ReadMe file and added the html file for the project

This commit is contained in:
derekc committed 2026-02-23 10:30:44 -08:00
1 parent 03c10184e2
commit ce6b01dacb
2 files changed
+327 -2

No files matched your search

+284
View File
@@ -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>