Refactor app to React Router and Tailwind

This commit is contained in:
TopherMayor
2026-06-12 10:35:19 -07:00
parent 11f5d1b225
commit fa0a7f44b7
15 changed files with 3157 additions and 318 deletions

View File

@@ -9,9 +9,14 @@
<style>
/* ── Map tab ─────────────────────────────────────────────── */
#map-view {
position: relative;
height: calc(100vh - 120px);
min-height: 400px;
position: sticky;
top: 86px;
height: calc(100vh - 118px);
min-height: 560px;
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
background: #0a0a14;
}
#cabo-map {
position: absolute;
@@ -598,7 +603,21 @@
.tab.active .tab-count { background: rgba(0,212,255,0.15); color: var(--accent); }
/* ── Main content ──────────────────────────────────────── */
main { flex: 1; overflow-y: auto; padding: 16px; max-width: 700px; margin: 0 auto; width: 100%; }
main { flex: 1; overflow-y: auto; padding: 16px; max-width: 1440px; margin: 0 auto; width: 100%; }
.content-shell {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(360px, 42vw);
gap: 16px;
align-items: start;
}
.list-pane,
.map-pane {
min-width: 0;
}
.mobile-view-toggle {
display: none;
}
/* ── Status bar ─────────────────────────────────────────── */
.status-bar {
@@ -659,6 +678,32 @@
font-size: 0.85rem;
font-weight: 700;
}
.option-media {
display: grid;
grid-template-columns: minmax(112px, 34%) 1fr;
gap: 14px;
align-items: stretch;
}
.option-image-wrap {
min-height: 158px;
border-radius: 10px;
overflow: hidden;
background:
linear-gradient(135deg, rgba(0,212,255,0.16), rgba(251,191,36,0.10)),
var(--surface2);
border: 1px solid rgba(255,255,255,0.06);
}
.option-image {
width: 100%;
height: 100%;
min-height: 158px;
display: block;
object-fit: cover;
filter: saturate(1.05) contrast(1.02);
}
.option-content {
min-width: 0;
}
.option-actions {
display: flex;
align-items: center;
@@ -667,6 +712,25 @@
margin: 10px 0 2px;
flex-wrap: wrap;
}
.option-book-btn {
border: 1px solid rgba(251,191,36,0.38);
background: rgba(251,191,36,0.12);
color: #ffefbf;
border-radius: 999px;
padding: 7px 12px;
font-size: 0.74rem;
font-weight: 800;
letter-spacing: 0.2px;
cursor: pointer;
text-decoration: none;
transition: transform 0.15s, border-color 0.2s, background 0.2s;
}
.option-book-btn:hover {
border-color: rgba(251,191,36,0.62);
background: rgba(251,191,36,0.18);
transform: translateY(-1px);
text-decoration: none;
}
.option-vote-btn {
border: 1px solid rgba(0,212,255,0.32);
background: rgba(0,212,255,0.10);
@@ -1451,9 +1515,54 @@
/* Main adjusts for bottom tabs */
main { padding: 12px; max-width: 100%; }
.content-shell {
display: block;
}
.mobile-view-toggle {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
margin-bottom: 12px;
padding: 4px;
border: 1px solid var(--border);
border-radius: 12px;
background: rgba(19,22,31,0.92);
}
.mobile-view-toggle button {
border: 1px solid transparent;
background: transparent;
color: var(--text-muted);
border-radius: 9px;
padding: 9px 10px;
font-size: 0.76rem;
font-weight: 800;
cursor: pointer;
}
.mobile-view-toggle button.active {
background: rgba(0,212,255,0.12);
border-color: rgba(0,212,255,0.35);
color: var(--accent);
}
body.mobile-map-active .list-pane,
body:not(.mobile-map-active) .map-pane {
display: none;
}
#map-view {
position: relative;
top: auto;
height: calc(100vh - 172px);
min-height: 420px;
}
/* Option cards — larger tap targets */
.option-card { min-height: 72px; padding: 14px 16px; }
.option-media {
grid-template-columns: 1fr;
}
.option-image-wrap,
.option-image {
min-height: 178px;
}
.option-name { font-size: 0.92rem; }
.option-desc { font-size: 0.76rem; }
.option-actions { justify-content: flex-start; }
@@ -1567,99 +1676,110 @@
<!-- Main -->
<main>
<div class="sort-bar" id="sortBar">
<label for="sortModeSelect">Sort by</label>
<select id="sortModeSelect" class="sort-select" onchange="setSortMode(this.value)">
<option value="vote-desc">Votes: High to Low</option>
<option value="vote-asc">Votes: Low to High</option>
<option value="price-asc">Price: Low to High</option>
<option value="price-desc">Price: High to Low</option>
</select>
<div class="mobile-view-toggle" id="mobileViewToggle" aria-label="Mobile view toggle">
<button type="button" id="mobileListBtn" class="active" onclick="setMobileView('list')">List</button>
<button type="button" id="mobileMapBtn" onclick="setMobileView('map')">Map</button>
</div>
<div class="status-bar">
<div><span class="status-dot" id="wsDot"></span><span id="wsStatus">Connecting…</span></div>
<div><span class="polls-badge open" id="pollsBadge">POLLS OPEN</span></div>
<div><span id="totalVotersCount"></span></div>
</div>
<div class="options-list" id="optionsList" role="tabpanel" aria-label="Voting options" aria-live="polite">
<div class="empty-state"><div class="empty-emoji"></div>Loading options…</div>
</div>
<!-- Map view -->
<div id="map-view" style="display:none;">
<div id="cabo-map"></div>
<div class="map-overlay">
<!-- Row 1: Multi-provider search -->
<div id="map-search-wrap">
<span style="color:#7a8499;font-size:0.8rem;padding-left:8px;flex-shrink:0;">🔍</span>
<input type="text" id="map-search-input" placeholder="Search Los Cabos…" autocomplete="off" />
<select id="flight-origin-select" aria-label="Flight origin airport">
<option value="LAX">LAX</option>
<option value="ONT">ONT</option>
<div class="content-shell">
<section class="list-pane" id="listPane">
<div class="sort-bar" id="sortBar">
<label for="sortModeSelect">Sort by</label>
<select id="sortModeSelect" class="sort-select" onchange="setSortMode(this.value)">
<option value="vote-desc">Votes: High to Low</option>
<option value="vote-asc">Votes: Low to High</option>
<option value="price-asc">Price: Low to High</option>
<option value="price-desc">Price: High to Low</option>
</select>
<div class="provider-tabs">
<button class="provider-tab active-yelp" id="tab-yelp" onclick="setProvider('yelp')">🍴 Yelp</button>
<button class="provider-tab" id="tab-osm" onclick="setProvider('osm')">📍 OSM</button>
<button class="provider-tab" id="tab-all" onclick="setProvider('all')">⚡ All</button>
</div>
<div class="status-bar">
<div><span class="status-dot" id="wsDot"></span><span id="wsStatus">Connecting…</span></div>
<div><span class="polls-badge open" id="pollsBadge">POLLS OPEN</span></div>
<div><span id="totalVotersCount"></span></div>
</div>
<div class="options-list" id="optionsList" role="tabpanel" aria-label="Voting options" aria-live="polite">
<div class="empty-state"><div class="empty-emoji"></div>Loading options…</div>
</div>
<!-- Add option -->
<div class="add-section">
<h3> Suggest a Place</h3>
<div class="form-grid">
<input type="text" id="addName" placeholder="Name of the place (required)" maxlength="80" />
<input type="text" id="addDesc" placeholder="Short description — price, vibe, what to expect…" maxlength="200" />
<textarea id="addDetails" placeholder="Details on separate lines — price, inclusions, caveats, or notes…" maxlength="500" rows="3" style="background:transparent;border:1px solid #252a38;border-radius:10px;color:#e0e6f0;padding:10px;resize:vertical;min-height:84px;"></textarea>
<input type="url" id="addUrl" placeholder="Website URL (optional)" />
<div class="btn-row">
<select id="addCategory">
<option value="hotel">🏨 Hotel</option>
<option value="flight">✈️ Flight</option>
<option value="golf">⛳ Golf</option>
<option value="nightlife">🎧 Nightlife</option>
<option value="excursion">🚤 Excursion</option>
<option value="itinerary">🗺️ Full Itinerary</option>
<option value="budget">💸 Budget Idea</option>
</select>
<button class="btn-primary" id="addBtn" onclick="submitNewOption()">Submit</button>
</div>
</div>
<button id="map-search-btn" onclick="mapDoSearch()"></button>
</div>
<div class="flight-shortcuts" aria-label="Flight search shortcuts">
<button class="flight-shortcut-btn primary" onclick="quickBook('flights-google')">Google Flights</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-kayak')">KAYAK</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-united')">United</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-delta')">Delta</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-alaska')">Alaska</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-expedia')">Expedia</button>
</div>
<div id="map-search-results"></div>
<!-- Row 2: Category filters -->
<div class="map-filter-row">
<span class="row-label">Show</span>
<button class="map-cat-btn active" id="cat-btn-hotel" onclick="mapToggleCat('hotel')">🏨 Hotels</button>
<button class="map-cat-btn active" id="cat-btn-golf" onclick="mapToggleCat('golf')">⛳ Golf</button>
<button class="map-cat-btn active" id="cat-btn-nightlife" onclick="mapToggleCat('nightlife')">🎧 Nightlife</button>
<button class="map-cat-btn active" id="cat-btn-excursion" onclick="mapToggleCat('excursion')">🚤 Excursions</button>
<button class="map-cat-btn active" id="cat-btn-itinerary" onclick="mapToggleCat('itinerary')">🗺️ Itineraries</button>
<button class="map-cat-btn" id="cat-btn-clear" onclick="mapClearAllCats()" style="margin-left:4px;border-color:#f87171;color:#f87171;">✕ Clear</button>
</div>
</div>
<div class="map-legend">
<h4>Legend</h4>
<div class="legend-item"><div class="legend-dot" style="background:#3b82f6"></div> Hotel</div>
<div class="legend-item"><div class="legend-dot" style="background:#22c55e"></div> Golf</div>
<div class="legend-item"><div class="legend-dot" style="background:#06b6d4"></div> Excursion</div>
<div class="legend-item"><div class="legend-dot" style="background:#a855f7"></div> Nightlife</div>
<div class="legend-item"><div class="legend-dot" style="background:#fbbf24"></div> Itinerary</div>
<div class="legend-item"><div class="legend-dot" style="background:#ff6b35;opacity:0.8"></div> Yelp</div>
<div class="legend-item"><div class="legend-dot legend-dot-osm"></div> OSM</div>
<div class="legend-item"><div class="legend-dot" style="background:conic-gradient(#ff6b35 33%, #fbbf24 33%, #fbbf24 66%, #00d4ff 66%);width:9px;height:9px;border-radius:50%;display:inline-block;border:1px solid rgba(0,0,0,0.3)"></div> All sources</div>
</div>
</div>
</section>
<!-- Add option -->
<div class="add-section">
<h3> Suggest a Place</h3>
<div class="form-grid">
<input type="text" id="addName" placeholder="Name of the place (required)" maxlength="80" />
<input type="text" id="addDesc" placeholder="Short description — price, vibe, what to expect…" maxlength="200" />
<textarea id="addDetails" placeholder="Details on separate lines — price, inclusions, caveats, or notes…" maxlength="500" rows="3" style="background:transparent;border:1px solid #252a38;border-radius:10px;color:#e0e6f0;padding:10px;resize:vertical;min-height:84px;"></textarea>
<input type="url" id="addUrl" placeholder="Website URL (optional)" />
<div class="btn-row">
<select id="addCategory">
<option value="hotel">🏨 Hotel</option>
<option value="flight">✈️ Flight</option>
<option value="golf">⛳ Golf</option>
<option value="nightlife">🎧 Nightlife</option>
<option value="excursion">🚤 Excursion</option>
<option value="itinerary">🗺️ Full Itinerary</option>
<option value="budget">💸 Budget Idea</option>
</select>
<button class="btn-primary" id="addBtn" onclick="submitNewOption()">Submit</button>
<aside class="map-pane" id="mapPane" aria-label="Map view">
<!-- Map view -->
<div id="map-view">
<div id="cabo-map"></div>
<div class="map-overlay">
<!-- Row 1: Multi-provider search -->
<div id="map-search-wrap">
<span style="color:#7a8499;font-size:0.8rem;padding-left:8px;flex-shrink:0;">🔍</span>
<input type="text" id="map-search-input" placeholder="Search Los Cabos…" autocomplete="off" />
<select id="flight-origin-select" aria-label="Flight origin airport">
<option value="LAX">LAX</option>
<option value="ONT">ONT</option>
</select>
<div class="provider-tabs">
<button class="provider-tab active-yelp" id="tab-yelp" onclick="setProvider('yelp')">🍴 Yelp</button>
<button class="provider-tab" id="tab-osm" onclick="setProvider('osm')">📍 OSM</button>
<button class="provider-tab" id="tab-all" onclick="setProvider('all')">⚡ All</button>
</div>
<button id="map-search-btn" onclick="mapDoSearch()"></button>
</div>
<div class="flight-shortcuts" aria-label="Flight search shortcuts">
<button class="flight-shortcut-btn primary" onclick="quickBook('flights-google')">Google Flights</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-kayak')">KAYAK</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-united')">United</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-delta')">Delta</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-alaska')">Alaska</button>
<button class="flight-shortcut-btn" onclick="quickBook('flights-expedia')">Expedia</button>
</div>
<div id="map-search-results"></div>
<!-- Row 2: Category filters -->
<div class="map-filter-row">
<span class="row-label">Show</span>
<button class="map-cat-btn active" id="cat-btn-hotel" onclick="mapToggleCat('hotel')">🏨 Hotels</button>
<button class="map-cat-btn active" id="cat-btn-golf" onclick="mapToggleCat('golf')">⛳ Golf</button>
<button class="map-cat-btn active" id="cat-btn-nightlife" onclick="mapToggleCat('nightlife')">🎧 Nightlife</button>
<button class="map-cat-btn active" id="cat-btn-excursion" onclick="mapToggleCat('excursion')">🚤 Excursions</button>
<button class="map-cat-btn active" id="cat-btn-itinerary" onclick="mapToggleCat('itinerary')">🗺️ Itineraries</button>
<button class="map-cat-btn" id="cat-btn-clear" onclick="mapClearAllCats()" style="margin-left:4px;border-color:#f87171;color:#f87171;">✕ Clear</button>
</div>
</div>
<div class="map-legend">
<h4>Legend</h4>
<div class="legend-item"><div class="legend-dot" style="background:#3b82f6"></div> Hotel</div>
<div class="legend-item"><div class="legend-dot" style="background:#22c55e"></div> Golf</div>
<div class="legend-item"><div class="legend-dot" style="background:#06b6d4"></div> Excursion</div>
<div class="legend-item"><div class="legend-dot" style="background:#a855f7"></div> Nightlife</div>
<div class="legend-item"><div class="legend-dot" style="background:#fbbf24"></div> Itinerary</div>
<div class="legend-item"><div class="legend-dot" style="background:#ff6b35;opacity:0.8"></div> Yelp</div>
<div class="legend-item"><div class="legend-dot legend-dot-osm"></div> OSM</div>
<div class="legend-item"><div class="legend-dot" style="background:conic-gradient(#ff6b35 33%, #fbbf24 33%, #fbbf24 66%, #00d4ff 66%);width:9px;height:9px;border-radius:50%;display:inline-block;border:1px solid rgba(0,0,0,0.3)"></div> All sources</div>
</div>
</div>
</div>
</aside>
</div>
</main>