feat(ui): mobile-first redesign — compact hero, inline submit button, map-first with collapsible list
- Hero: remove full-width mobile submit, add inline "Go" button next to locate
- Prediction cards: tighter mobile padding (px-3 py-3)
- Search filters: right-aligned toolbar, remove "X stations found" count and map toggle
- Map: initialize view immediately to avoid tile wiggle, skip recenter on fresh init
- Station list: hidden by default, toggled via "Stations {count}" pill above map
- Typography: hide desktop h1 on mobile, scale down section headings and spacing
- Footer: remove uppercase styling from headings and copyright line
- Filter popover: auto-close on fuel/radius/sort/brand selection
fix(llm): retry submit_overlay when events_cited is missing, extend Fuel Finder timeout with retries
- LlmOverlayService: add `minItems: 1` to events_cited schema, detect missing citations
in submit response, inject tool_result error and retry once with explicit prompt
- Log full raw_result context when no verified citations, capturing direction/confidence/reasoning
- FuelPriceService: add 3×1s retry with 60s timeout to batch price requests (was 30s no retry)
- Tests: cover successful retry recovery and rejection when retry also omits citations
This commit is contained in:
@@ -5,12 +5,12 @@
|
||||
|
||||
<div class="hero-gradient">
|
||||
<!-- Hero -->
|
||||
<section id="hero" class="relative pt-24 md:pt-36 pb-4 md:pb-8 px-6 overflow-hidden">
|
||||
<section id="hero" class="relative pt-24 md:pt-36 pb-2 md:pb-8 px-3 overflow-hidden">
|
||||
<div class="max-w-7xl mx-auto grid lg:grid-cols-[1.1fr_1fr] gap-10 lg:gap-16 items-center">
|
||||
<div class="space-y-6 md:space-y-8">
|
||||
<div class="space-y-3 md:space-y-8">
|
||||
<LiveTicker :latest-price-at="liveStats.latestPriceAt" :station-count="liveStats.stationCount" />
|
||||
|
||||
<h1 class="font-serif text-zinc-900 text-[40px] leading-[0.98] md:text-6xl lg:text-[88px] lg:leading-[0.95] max-w-[560px]">
|
||||
<h1 class="hidden lg:block font-serif text-zinc-900 text-[40px] leading-[0.98] md:text-6xl lg:text-[88px] lg:leading-[0.95] max-w-140">
|
||||
Know <span class="text-accent">exactly</span> when to fill up.
|
||||
</h1>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
</section>
|
||||
|
||||
<!-- Search Results -->
|
||||
<section v-if="searchAttempted" id="searchAttempted" class="px-6">
|
||||
<div class="max-w-7xl mx-auto space-y-6">
|
||||
<section v-if="searchAttempted" id="searchAttempted" class="px-3">
|
||||
<div class="max-w-7xl mx-auto space-y-3">
|
||||
|
||||
<!-- Prediction box (sits above filter results) -->
|
||||
<PredictionCard
|
||||
@@ -48,10 +48,7 @@
|
||||
v-model:brand-filter="brandFilter"
|
||||
:brands="availableBrands"
|
||||
:initial="searchInitial"
|
||||
:map-open="mapOpen"
|
||||
:station-count="filteredStations.length"
|
||||
@search="onSearch"
|
||||
@toggle-map="mapOpen = !mapOpen"
|
||||
/>
|
||||
|
||||
<!-- Loading -->
|
||||
@@ -76,7 +73,6 @@
|
||||
</div>
|
||||
<template v-else>
|
||||
<LeafletMap
|
||||
:is-open="mapOpen"
|
||||
:origin="searchOrigin"
|
||||
:radius-miles="radiusMiles"
|
||||
:selected-station-id="selectedStationId"
|
||||
@@ -111,11 +107,28 @@
|
||||
</template>
|
||||
</LeafletMap>
|
||||
<UpsellBanner :station-count="liveStats.stationCount" />
|
||||
<StationList
|
||||
:current-sort="sort"
|
||||
:origin="searchOrigin"
|
||||
:stations="filteredStations"
|
||||
/>
|
||||
|
||||
<button
|
||||
:aria-expanded="listOpen"
|
||||
:class="listOpen ? '' : 'mb-6'"
|
||||
aria-controls="station-list-panel"
|
||||
class="pill w-full justify-center"
|
||||
type="button"
|
||||
@click="listOpen = !listOpen"
|
||||
>
|
||||
<iconify-icon :icon="listOpen ? 'lucide:chevron-up' : 'lucide:list'" class="text-sm opacity-70"></iconify-icon>
|
||||
<span class="text-sm font-medium">
|
||||
{{ listOpen ? 'Hide list' : `Stations ${filteredStations.length}` }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div v-if="listOpen" id="station-list-panel">
|
||||
<StationList
|
||||
:current-sort="sort"
|
||||
:origin="searchOrigin"
|
||||
:stations="filteredStations"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -124,41 +137,41 @@
|
||||
</div>
|
||||
|
||||
<!-- How It Works -->
|
||||
<section id="how-it-works" class="py-12 md:py-24 px-6 bg-zinc-50">
|
||||
<section id="how-it-works" class="py-4 md:py-24 px-3 bg-zinc-50">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center mb-16 space-y-4">
|
||||
<h2 class="text-4xl md:text-5xl font-black font-display text-zinc-800">Smart Savings in 3 Steps</h2>
|
||||
<p class="text-zinc-500 text-lg max-w-2xl mx-auto">Stop guessing when to fill up. Our engine analyzes thousands of data points daily to save you money.</p>
|
||||
<div class="text-center mb-4 md:mb-8 space-y-2 md:space-y-4">
|
||||
<h2 class="text-xl md:text-5xl font-black font-display text-zinc-800">Smart Savings in 3 Steps</h2>
|
||||
<p class="text-zinc-500 text-sm md:text-lg max-w-2xl mx-auto">Stop guessing when to fill up. Our engine analyzes thousands of data points daily to save you money.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-12">
|
||||
<div class="text-center space-y-4">
|
||||
<div class="text-center space-y-2 md:space-y-4">
|
||||
<div class="w-16 h-16 bg-accent/10 text-accent rounded-2xl flex items-center justify-center mx-auto text-3xl">
|
||||
<iconify-icon icon="lucide:search"></iconify-icon>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold font-display">1. Search</h3>
|
||||
<p class="text-zinc-500">Enter your postcode or location to find every forecourt within a 5–20 mile radius instantly.</p>
|
||||
<h3 class="text-xl md:text-5xl font-bold font-display">1. Search</h3>
|
||||
<p class="text-zinc-500 text-sm md:text-lg">Enter your postcode or location to find every forecourt within a 5–20 mile radius instantly.</p>
|
||||
</div>
|
||||
<div class="text-center space-y-4">
|
||||
<div class="text-center space-y-2 md:space-y-4">
|
||||
<div class="w-16 h-16 bg-accent/10 text-accent rounded-2xl flex items-center justify-center mx-auto text-3xl">
|
||||
<iconify-icon icon="lucide:trending-up"></iconify-icon>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold font-display">2. Get Advice</h3>
|
||||
<p class="text-zinc-500">Our AI compares local prices against national wholesale trends to give you a Fill Up / Wait recommendation.</p>
|
||||
<h3 class="text-sm md:text-lg font-bold font-display">2. Get Advice</h3>
|
||||
<p class="text-zinc-500 text-sm md:text-lg">Our AI compares local prices against national wholesale trends to give you a Fill Up / Wait recommendation.</p>
|
||||
</div>
|
||||
<div class="text-center space-y-4">
|
||||
<div class="text-center space-y-2 md:space-y-4">
|
||||
<div class="w-16 h-16 bg-accent/10 text-accent rounded-2xl flex items-center justify-center mx-auto text-3xl">
|
||||
<iconify-icon icon="lucide:wallet"></iconify-icon>
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold font-display">3. Fill Up Smart</h3>
|
||||
<p class="text-zinc-500">Navigate to the cheapest station and fill up with confidence knowing you've secured the best price.</p>
|
||||
<h3 class="text-sm md:text-lg font-bold font-display">3. Fill Up Smart</h3>
|
||||
<p class="text-zinc-500 text-sm md:text-lg">Navigate to the cheapest station and fill up with confidence knowing you've secured the best price.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section id="features" class="py-12 md:py-24 px-6">
|
||||
<section id="features" class="py-4 md:py-24 px-6">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="grid lg:grid-cols-2 gap-20 items-center">
|
||||
<div class="order-2 lg:order-1">
|
||||
@@ -187,8 +200,8 @@
|
||||
</div>
|
||||
|
||||
<div class="order-1 lg:order-2 space-y-8">
|
||||
<h2 class="text-4xl md:text-5xl font-black font-display text-zinc-800">The ultimate fuel companion.</h2>
|
||||
<p class="text-lg text-zinc-500">Whether you're a daily commuter, a delivery professional, or just planning a weekend road trip, FuelAlert gives you the edge at the pump.</p>
|
||||
<h2 class="text-xl md:text-5xl font-black font-display text-zinc-800">The ultimate fuel companion.</h2>
|
||||
<p class="text-sm md:text-lg text-zinc-500">Whether you're a daily commuter, a delivery professional, or just planning a weekend road trip, FuelAlert gives you the edge at the pump.</p>
|
||||
<ul class="space-y-4">
|
||||
<li class="flex items-center gap-3 font-bold">
|
||||
<iconify-icon class="text-accent" icon="lucide:check-circle-2"></iconify-icon>
|
||||
@@ -213,7 +226,7 @@
|
||||
</section>
|
||||
|
||||
<!-- Pricing -->
|
||||
<section id="pricing" class="py-12 md:py-24 px-6 bg-zinc-50">
|
||||
<section id="pricing" class="py-4 md:py-24 px-6 bg-zinc-50">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl md:text-5xl font-black font-display text-zinc-800 mb-4">Pricing for every driver</h2>
|
||||
@@ -367,7 +380,7 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<h5 class="font-black uppercase text-xs text-zinc-800 tracking-widest">Product</h5>
|
||||
<h5 class="font-black text-xs text-zinc-800 tracking-widest">Product</h5>
|
||||
<ul class="space-y-2 text-sm text-zinc-500">
|
||||
<li><a class="hover:text-accent transition-colors" href="#pricing">Pricing</a></li>
|
||||
<li><a class="hover:text-accent transition-colors" href="#features">Features</a></li>
|
||||
@@ -377,7 +390,7 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<h5 class="font-black uppercase text-xs text-zinc-800 tracking-widest">Resources</h5>
|
||||
<h5 class="font-black text-xs text-zinc-800 tracking-widest">Resources</h5>
|
||||
<ul class="space-y-2 text-sm text-zinc-500">
|
||||
<li><a class="hover:text-accent transition-colors" href="#">Market Insights</a></li>
|
||||
<li><a class="hover:text-accent transition-colors" href="#">How We Track</a></li>
|
||||
@@ -387,7 +400,7 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<h5 class="font-black uppercase text-xs text-zinc-800 tracking-widest">Legal</h5>
|
||||
<h5 class="font-black text-xs text-zinc-800 tracking-widest">Legal</h5>
|
||||
<ul class="space-y-2 text-sm text-zinc-500">
|
||||
<li><a class="hover:text-accent transition-colors" href="#">Privacy Policy</a></li>
|
||||
<li><a class="hover:text-accent transition-colors" href="#">Terms of Service</a></li>
|
||||
@@ -396,7 +409,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto pt-8 border-t border-zinc-300 flex flex-col md:flex-row justify-between items-center gap-4 text-[10px] font-bold uppercase tracking-widest text-zinc-500">
|
||||
<div class="max-w-7xl mx-auto pt-8 border-t border-zinc-300 flex flex-col md:flex-row justify-between items-center gap-4 text-[10px] tracking-widest text-zinc-500">
|
||||
<p>© 2024 FuelAlert UK Limited. All Rights Reserved.</p>
|
||||
<p>Data provided by official UK retail price transparency schemes.</p>
|
||||
<p>Postcode data from <a class="underline hover:text-accent" href="https://geoportal.statistics.gov.uk/datasets/ons::onspd-online-latest-centroids-1/about" rel="noopener" target="_blank">ONS Postcode Directory</a>: contains OS data © Crown copyright & database right, Royal Mail data © Royal Mail copyright & database right, and National Statistics data © Crown copyright & database right.</p>
|
||||
@@ -501,23 +514,23 @@ const searchAttempted = ref(false)
|
||||
const radiusMiles = ref(10)
|
||||
const brandFilter = ref('')
|
||||
|
||||
const MAP_STORAGE_KEY = 'fuel-price:map-open'
|
||||
const LIST_STORAGE_KEY = 'fuelalert:list-open'
|
||||
|
||||
function readSavedMapOpen() {
|
||||
function readSavedListOpen() {
|
||||
try {
|
||||
const v = localStorage.getItem(MAP_STORAGE_KEY)
|
||||
if (v === null) return true
|
||||
const v = localStorage.getItem(LIST_STORAGE_KEY)
|
||||
if (v === null) return false
|
||||
return v === '1'
|
||||
} catch {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const mapOpen = ref(readSavedMapOpen())
|
||||
const listOpen = ref(readSavedListOpen())
|
||||
|
||||
watch(mapOpen, (v) => {
|
||||
watch(listOpen, (v) => {
|
||||
try {
|
||||
localStorage.setItem(MAP_STORAGE_KEY, v ? '1' : '0')
|
||||
localStorage.setItem(LIST_STORAGE_KEY, v ? '1' : '0')
|
||||
} catch {
|
||||
// ignore quota / privacy-mode errors
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user