Refactor app to React Router and Tailwind
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user