chore: initial checkpoint - Tesla Roadtrip planner
- Proactive Grok integration (xAI API + local CLI fallback) - Real road routing via OSRM (no more bird's-eye lines) - Heavy structured logging for fast iteration - Strong sanitization + geocoding + ErrorBoundary (no black screens) - Playwright E2E tests (API diagnostic + full UI flow) - scripts/dev.sh for one-command startup - Clean .env.example + documentation This is a stable checkpoint before further prompt/UI refinement.
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tesla Roadtrip • Grok Drive — UI Preview</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Space+Grotesk:wght@500;600&display=swap');
|
||||
|
||||
:root {
|
||||
--tesla-red: #E82127;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system_ui, sans-serif;
|
||||
}
|
||||
|
||||
.font-display {
|
||||
font-family: 'Space Grotesk', 'Inter', system_ui, sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tesla-red { color: #E82127; }
|
||||
.bg-tesla-red { background-color: #E82127; }
|
||||
|
||||
.chat-bubble-user {
|
||||
background: #E82127;
|
||||
color: white;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-bubble-assistant {
|
||||
background: #1f242e;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
background: #0a0f1a;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.map-grid {
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,0.035) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.035) 1px, transparent 1px);
|
||||
background-size: 42px 42px;
|
||||
}
|
||||
|
||||
.tesla-marker {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
border: 2px solid white;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.stop-row {
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.stop-row:hover {
|
||||
background-color: #22283a;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 10px;
|
||||
letter-spacing: 1.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-[#0a0a0a] text-white">
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
|
||||
<!-- LEFT: CHAT -->
|
||||
<div class="w-[380px] flex flex-col border-r border-white/10 bg-[#111111]">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="px-5 py-4 border-b border-white/10 flex items-center justify-between bg-black/60">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-full bg-[#E82127] flex items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.25" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold text-[17px] tracking-[-0.4px]">Grok Drive</div>
|
||||
<div class="text-[10px] text-white/50 -mt-0.5">UK & Europe • Headless Grok</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[10px] px-2.5 py-0.5 rounded bg-white/5 font-mono tracking-widest text-white/60">v0.1</div>
|
||||
</div>
|
||||
|
||||
<!-- Vehicle Selector -->
|
||||
<div class="px-4 py-3 border-b border-white/10 bg-black/30">
|
||||
<div class="text-[10px] uppercase tracking-[1.5px] text-white/40 mb-1.5 px-1">YOUR VEHICLE</div>
|
||||
<select class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-4 py-[9px] text-sm focus:outline-none focus:border-[#E82127]">
|
||||
<option>Model Y Long Range — 514 km</option>
|
||||
<option>Model 3 Highland LR — 549 km</option>
|
||||
<option>Model Y RWD (EU) — 455 km</option>
|
||||
<option>Model S Long Range — 634 km</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Messages -->
|
||||
<div class="flex-1 overflow-y-auto p-4 space-y-6 text-sm" id="chat-messages">
|
||||
|
||||
<!-- Assistant message -->
|
||||
<div class="flex">
|
||||
<div class="max-w-[85%] chat-bubble-assistant rounded-2xl px-4 py-3 leading-snug">
|
||||
Hello! I'm Grok Drive, your Tesla trip planner for the UK and Europe. Where would you like to go? I know the Supercharger network across Britain, France, Germany, and beyond extremely well.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User message -->
|
||||
<div class="flex justify-end">
|
||||
<div class="max-w-[82%] chat-bubble-user rounded-2xl px-4 py-3 leading-snug">
|
||||
Plan a 2-day trip from London to Edinburgh in my Model Y
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assistant reply -->
|
||||
<div class="flex">
|
||||
<div class="max-w-[85%] chat-bubble-assistant rounded-2xl px-4 py-3 leading-snug">
|
||||
Excellent choice — London to Edinburgh is one of the most popular Tesla routes in the UK. I've routed you via the excellent M1/A1 corridor with fast V3/V4 stalls. Battery stays healthy throughout.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Quick Prompts -->
|
||||
<div class="px-3 pb-2 flex flex-wrap gap-1.5 border-t border-white/10 pt-3 bg-black/40">
|
||||
<div onclick="useQuickPrompt(this)" class="cursor-pointer text-[10px] px-3 py-1 bg-white/5 hover:bg-white/10 rounded-full border border-white/10 transition">Plan a 2-day trip from London to Edinburgh</div>
|
||||
<div onclick="useQuickPrompt(this)" class="cursor-pointer text-[10px] px-3 py-1 bg-white/5 hover:bg-white/10 rounded-full border border-white/10 transition">Add a lunch stop in the Lake District</div>
|
||||
<div onclick="useQuickPrompt(this)" class="cursor-pointer text-[10px] px-3 py-1 bg-white/5 hover:bg-white/10 rounded-full border border-white/10 transition">Find a hotel with destination charging</div>
|
||||
</div>
|
||||
|
||||
<!-- Input -->
|
||||
<div class="p-3 border-t border-white/10 bg-black/40">
|
||||
<div class="flex items-center gap-2 bg-[#1a1a1a] border border-white/10 rounded-2xl px-3 focus-within:border-[#E82127]">
|
||||
<input id="chat-input"
|
||||
type="text"
|
||||
placeholder="Where would you like to drive in the UK or Europe?"
|
||||
class="flex-1 bg-transparent py-3 text-sm placeholder:text-white/40 outline-none"
|
||||
onkeypress="if(event.key === 'Enter') sendMessage()">
|
||||
<button onclick="sendMessage()"
|
||||
class="p-2 rounded-xl bg-[#E82127] hover:bg-[#c01a20] active:scale-95 transition flex items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-center text-[9px] text-white/30 mt-2 tracking-[1.5px]">POWERED BY HEADLESS GROK CLI</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: MAP + ITINERARY -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
|
||||
<!-- Stats Bar -->
|
||||
<div class="h-14 border-b border-white/10 bg-black/40 px-6 flex items-center justify-between text-sm">
|
||||
<div class="flex items-center gap-8 nav-text">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-[#E82127]"><svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314-11.314z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" /></svg></span>
|
||||
<span class="font-semibold">665</span>
|
||||
<span class="text-white/50">km</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-[#E82127]"><svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 01-9 9 9 9 0 01-9-9 9 9 0 019-9 9 9 0 019 9z" /></svg></span>
|
||||
<span class="font-semibold">7.2</span>
|
||||
<span class="text-white/50">hrs drive</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-[#E82127]"><svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 inline" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg></span>
|
||||
<span class="font-semibold">1.1</span>
|
||||
<span class="text-white/50">hrs charging</span>
|
||||
</div>
|
||||
<div class="text-white/50">• 3 Superchargers • 1 hotel</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button onclick="alert('GPX would be downloaded in the real app')"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-xs border border-white/20 rounded-lg hover:bg-white/5 transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 13v6m0 0l-3-3m3 3l3-3m-6-4v1m0 0l-3-3m3 3l3-3" /></svg>
|
||||
GPX
|
||||
</button>
|
||||
<button onclick="alert('Share link copied in the real app')"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-xs bg-white/5 hover:bg-white/10 rounded-lg transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 01-2.25 2.25" /></svg>
|
||||
Share
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MAP -->
|
||||
<div class="flex-1 p-3 bg-[#05070d]">
|
||||
<div class="w-full h-full rounded-2xl overflow-hidden border border-white/10 relative" id="map-wrapper">
|
||||
<!-- Real Leaflet will be initialized here -->
|
||||
<div id="map" style="width: 100%; height: 100%; background: #0a0f1a;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Itinerary -->
|
||||
<div class="h-[215px] border-t border-white/10 bg-[#111111] p-4 overflow-x-auto">
|
||||
<div class="flex gap-6 min-w-max">
|
||||
|
||||
<!-- Day 1 -->
|
||||
<div class="w-[310px]">
|
||||
<div class="uppercase text-xs tracking-[2px] text-[#E82127] mb-2">DAY 1 • London → Edinburgh</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<!-- Stop -->
|
||||
<div class="flex justify-between items-center bg-[#1a1f2b] border border-white/5 rounded-2xl px-4 py-2.5 text-sm group">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2.5 h-2.5 rounded-full bg-[#E82127] flex-shrink-0"></div>
|
||||
<div>
|
||||
<div class="font-medium">London Battersea Supercharger</div>
|
||||
<div class="text-xs text-white/40">Arrive 28% • 22 min charge</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="removeStop(this)" class="opacity-0 group-hover:opacity-100 text-white/30 hover:text-red-400"><svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.595 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.595-1.858L5 7m5 4v6m4-6v6m1-10V9a1 1 0 00-1-1h-4a1 1 0 00-1 1v1M9 7h6" /></svg></button>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center bg-[#1a1f2b] border border-white/5 rounded-2xl px-4 py-2.5 text-sm group">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2.5 h-2.5 rounded-full bg-[#E82127] flex-shrink-0"></div>
|
||||
<div>
|
||||
<div class="font-medium">Birmingham Supercharger</div>
|
||||
<div class="text-xs text-white/40">Arrive 19% • 28 min charge</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="removeStop(this)" class="opacity-0 group-hover:opacity-100 text-white/30 hover:text-red-400"><svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.595 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.595-1.858L5 7m5 4v6m4-6v6m1-10V9a1 1 0 00-1-1h-4a1 1 0 00-1 1v1M9 7h6" /></svg></button>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center bg-[#1a1f2b] border border-white/5 rounded-2xl px-4 py-2.5 text-sm group">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2.5 h-2.5 rounded-full bg-blue-500 flex-shrink-0"></div>
|
||||
<div>
|
||||
<div class="font-medium">The Balmoral Hotel, Edinburgh</div>
|
||||
<div class="text-xs text-white/40">Destination charging • 4 nights</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="removeStop(this)" class="opacity-0 group-hover:opacity-100 text-white/30 hover:text-red-400"><svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.595 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.595-1.858L5 7m5 4v6m4-6v6m1-10V9a1 1 0 00-1-1h-4a1 1 0 00-1 1v1M9 7h6" /></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Day 2 -->
|
||||
<div class="w-[310px]">
|
||||
<div class="uppercase text-xs tracking-[2px] text-[#E82127] mb-2">DAY 2 • Edinburgh & Return</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex justify-between items-center bg-[#1a1f2b] border border-white/5 rounded-2xl px-4 py-2.5 text-sm group">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2.5 h-2.5 rounded-full bg-white/60 flex-shrink-0"></div>
|
||||
<div>
|
||||
<div class="font-medium">Edinburgh Castle</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="removeStop(this)" class="opacity-0 group-hover:opacity-100 text-white/30 hover:text-red-400"><svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.595 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.595-1.858L5 7m5 4v6m4-6v6m1-10V9a1 1 0 00-1-1h-4a1 1 0 00-1 1v1M9 7h6" /></svg></button>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center bg-[#1a1f2b] border border-white/5 rounded-2xl px-4 py-2.5 text-sm group">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2.5 h-2.5 rounded-full bg-[#E82127] flex-shrink-0"></div>
|
||||
<div>
|
||||
<div class="font-medium">Glasgow Supercharger</div>
|
||||
<div class="text-xs text-white/40">Arrive 24% • 19 min charge</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="removeStop(this)" class="opacity-0 group-hover:opacity-100 text-white/30 hover:text-red-400"><svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.595 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.595-1.858L5 7m5 4v6m4-6v6m1-10V9a1 1 0 00-1-1h-4a1 1 0 00-1 1v1M9 7h6" /></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script>
|
||||
// Tailwind script
|
||||
function initializeTailwind() {
|
||||
document.documentElement.style.setProperty('--accent', '#E82127');
|
||||
}
|
||||
|
||||
// Initialize Leaflet Map (Europe / UK view)
|
||||
function initMap() {
|
||||
const map = L.map('map', {
|
||||
zoomControl: false,
|
||||
attributionControl: false
|
||||
}).setView([54.8, -2.8], 5.4);
|
||||
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://carto.com/attributions">CARTO</a>', subdomains: 'abcd', maxZoom: 20
|
||||
}).addTo(map);
|
||||
|
||||
// Stops for London → Edinburgh route
|
||||
const stops = [
|
||||
{ id: 1, name: "London Battersea", lat: 51.477, lng: -0.17, type: "supercharger" },
|
||||
{ id: 2, name: "Birmingham", lat: 52.486, lng: -1.89, type: "supercharger" },
|
||||
{ id: 3, name: "The Balmoral Hotel", lat: 55.953, lng: -3.188, type: "hotel" },
|
||||
{ id: 4, name: "Edinburgh Castle", lat: 55.9486, lng: -3.2008, type: "attraction" }
|
||||
];
|
||||
|
||||
// Add markers
|
||||
stops.forEach(stop => {
|
||||
const color = stop.type === 'supercharger' ? '#E82127' :
|
||||
stop.type === 'hotel' ? '#3b82f6' : '#64748b';
|
||||
|
||||
const icon = L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `<div style="background:${color}; width:22px; height:22px; border-radius:9999px; border:2px solid white; display:flex; align-items:center; justify-content:center; box-shadow:0 2px 6px rgba(0,0,0,0.3);">
|
||||
<span style="color:white; font-size:10px; font-weight:700;">${stop.type === 'supercharger' ? '⚡' : '●'}</span>
|
||||
</div>`,
|
||||
iconSize: [22, 22],
|
||||
iconAnchor: [11, 11]
|
||||
});
|
||||
|
||||
L.marker([stop.lat, stop.lng], { icon })
|
||||
.addTo(map)
|
||||
.bindPopup(`<b>${stop.name}</b><br><span style="color:#888">${stop.type}</span>`);
|
||||
});
|
||||
|
||||
// Draw route polyline
|
||||
const routeCoords = stops.map(s => [s.lat, s.lng]);
|
||||
L.polyline(routeCoords, {
|
||||
color: '#E82127',
|
||||
weight: 4,
|
||||
opacity: 0.7,
|
||||
lineJoin: 'round'
|
||||
}).addTo(map);
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
const input = document.getElementById('chat-input');
|
||||
if (!input.value.trim()) return;
|
||||
|
||||
const messagesContainer = document.getElementById('chat-messages');
|
||||
|
||||
// Add user message
|
||||
const userMsg = document.createElement('div');
|
||||
userMsg.className = 'flex justify-end';
|
||||
userMsg.innerHTML = `
|
||||
<div class="max-w-[82%] chat-bubble-user rounded-2xl px-4 py-3 leading-snug">
|
||||
${input.value}
|
||||
</div>
|
||||
`;
|
||||
messagesContainer.appendChild(userMsg);
|
||||
|
||||
const userText = input.value;
|
||||
input.value = '';
|
||||
|
||||
// Simulate thinking
|
||||
setTimeout(() => {
|
||||
const thinking = document.createElement('div');
|
||||
thinking.className = 'flex items-center gap-2 pl-1 text-[#E82127]';
|
||||
thinking.innerHTML = `
|
||||
<div class="flex gap-1">
|
||||
<div class="w-1 h-1 bg-current rounded-full animate-bounce"></div>
|
||||
<div class="w-1 h-1 bg-current rounded-full animate-bounce" style="animation-delay:120ms"></div>
|
||||
<div class="w-1 h-1 bg-current rounded-full animate-bounce" style="animation-delay:240ms"></div>
|
||||
</div>
|
||||
<span class="text-xs tracking-widest">GROK IS PLANNING...</span>
|
||||
`;
|
||||
messagesContainer.appendChild(thinking);
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
|
||||
setTimeout(() => {
|
||||
thinking.remove();
|
||||
|
||||
const reply = document.createElement('div');
|
||||
reply.className = 'flex';
|
||||
reply.innerHTML = `
|
||||
<div class="max-w-[85%] chat-bubble-assistant rounded-2xl px-4 py-3 leading-snug">
|
||||
I've added a great lunch stop at Tebay Services in the Lake District. Many Tesla owners rate it as one of the best on this route.
|
||||
</div>
|
||||
`;
|
||||
messagesContainer.appendChild(reply);
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
|
||||
// Simulate itinerary update
|
||||
setTimeout(() => {
|
||||
alert("In the real app, this would dynamically add a new stop to the map and itinerary panel.");
|
||||
}, 800);
|
||||
}, 1400);
|
||||
}, 900);
|
||||
}
|
||||
|
||||
function useQuickPrompt(el) {
|
||||
const text = el.textContent;
|
||||
const input = document.getElementById('chat-input');
|
||||
input.value = text;
|
||||
|
||||
// Trigger send
|
||||
setTimeout(() => {
|
||||
sendMessage();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function removeStop(btn) {
|
||||
btn.closest('.group').style.transition = 'all 0.2s';
|
||||
btn.closest('.group').style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
btn.closest('.group').remove();
|
||||
}, 180);
|
||||
}
|
||||
|
||||
// Boot everything
|
||||
function init() {
|
||||
initializeTailwind();
|
||||
initMap();
|
||||
|
||||
// Keyboard hint
|
||||
const input = document.getElementById('chat-input');
|
||||
input.addEventListener('focus', () => {
|
||||
input.placeholder = "Try: 'Add a stop near the Lake District'";
|
||||
});
|
||||
}
|
||||
|
||||
window.onload = init;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
echo "High-fidelity static UI preview created"
|
||||
Reference in New Issue
Block a user