# FuelFinder Mobile Landing Page — Design Spec

**Date:** 2026-04-07  
**Scope:** Replace static mobile homepage with a fully functional Livewire-powered landing page, backed by reusable Blade components shared with the desktop search.

---

## Goals

- Mobile landing page (`/`) becomes the working app — search, recommendation, map, stations list, forecast
- All interactive sections are driven by a single new Livewire component (`FuelFinder`)
- Presentation elements extracted into reusable Blade components consumed by both `FuelFinder` and the existing `StationSearch`
- As lean as possible — no duplicated logic, one station repeater, one map component

---

## Route Change

```php
// routes/web.php
Route::get('/', FuelFinder::class)->name('home');
```

`/stations` (StationSearch) is kept as-is for now. Migration to shared components happens separately.

---

## Livewire Component: `FuelFinder`

**File:** `app/Livewire/Public/FuelFinder.php`  
**View:** `resources/views/livewire/public/fuel-finder.blade.php`

### Properties

| Property | Type | Default | Notes |
|---|---|---|---|
| `$search` | `string` | `''` | Validated: required |
| `$fuelType` | `string` | `'petrol'` | Validated: required |
| `$radius` | `int` | `5` | Validated: 1–20 |
| `$sort` | `string` | `'reliable'` | Validated: in allowed list |
| `$results` | `array` | `[]` | Populated from `/api/stations` |
| `$meta` | `array` | `[]` | Count, lowest/avg price |
| `$prediction` | `?array` | `null` | Populated from `/api/prediction` |
| `$apiError` | `?string` | `null` | Surface API/connection errors |
| `$hasSearched` | `bool` | `false` | Controls section visibility |

### Methods

**`findStations()`**
1. Validate all properties
2. Reset results, meta, prediction, apiError, hasSearched
3. Call `GET /api/stations` with postcode, fuel_type, radius (km), sort
4. On success: populate `$results`, `$meta`, set `$hasSearched = true`
5. Call `GET /api/prediction` with `fuel_type` (no lat/lng — national fallback)
6. On success: populate `$prediction`
7. On any failure: set `$apiError`

**`updatedFuelType()` / `updatedRadius()` / `updatedSort()`**  
Re-run `findStations()` if `$hasSearched` is true (live filter refresh).

---

## Blade Components

All components live under `resources/views/components/fuel/` and are namespaced as `x-fuel.*`.

### `x-fuel.type-select`
Fuel type `<select>`. Accepts `wire:model` passthrough via `$attributes->whereStartsWith('wire:')`.  
Options: Petrol (E10), Super Unleaded (E5), Diesel, Premium Diesel, B10 Biodiesel, HVO.  
Reused in: `FuelFinder`, `StationSearch`.

### `x-fuel.radius-select`
Radius `<select>`. Options: 1, 2, 5, 10, 20 miles.  
Accepts `wire:model` passthrough.

### `x-fuel.sort-select`
Sort `<select>`. Options: Best price (reliable), Cheapest first, Nearest first, Recently updated, Brand A–Z.  
Accepts `wire:model` passthrough.

### `x-fuel.station-card :station`
Single station row. Props from `/api/stations` response:
- Name, address, postcode, distance (km → miles)
- Price in pence (formatted to 1dp)
- Price colour: `current` → green, `recent` → slate, `stale` → amber, `outdated` → red
- `is_supermarket` → shows "Supermarket" badge

### `x-fuel.station-map :results`
Leaflet map wrapper using Alpine `x-data="stationMap(@entangle('results'))"` pattern.  
- Default centre: UK (`54.0, -2.0`), zoom 6 — shown before search
- After search: re-centres to fit result bounds automatically (handled in `station-map.js`)
- Height: `h-56` mobile / `h-96` desktop

### `x-fuel.recommendation :prediction`
Recommendation card driven by `/api/prediction` response.  
- Shows `action` as headline: `fill_now` → "Fill up now", `wait` → "Wait", else "No signal"
- Confidence ring (SVG) from `confidence_score` (0–100)
- `reasoning` text below
- Confidence label badge (`confidence_label`: low / medium / high)
- Hidden when `$prediction` is null

### `x-fuel.forecast`
Static 14-day forecast Pro upsell card.  
- SVG squiggle line chart (decorative)
- Blurred/locked overlay with "Unlock Forecast" CTA button
- "Pro" badge

### `x-mobile-header`
Mobile app header: FuelAlert logo + user icon button (links to login/dashboard based on auth state).  
`pt-14` for safe area, fixed positioning.

### `x-mobile-footer`
Mobile tab bar: Prices, Alerts, Forecourts, Trends. Sticky bottom, `pb-8` for safe area.  
Active tab highlight for current route.

---

## View: `fuel-finder.blade.php`

Mobile layout only (desktop layout handled by existing homepage / StationSearch).  
Structure:

```
<x-mobile-header />

<main> {{-- scrollable --}}

  {{-- #search --}}
  Search input + x-fuel.type-select + x-fuel.radius-select + x-fuel.sort-select
  Displayed as pill-style filter buttons (scroll horizontally on mobile)
  Submit triggers findStations()

  {{-- #recommendation --}}
  <x-fuel.recommendation :prediction="$prediction" />
  Hidden (@if $prediction) until searched

  {{-- #map --}}
  <x-fuel.station-map :results="$results" />
  Always visible — UK centre until search

  {{-- #stations --}}
  @if $hasSearched
    @forelse $results as $station
      <x-fuel.station-card :station="$station" />
    @empty
      "No stations found" message
    @endforelse
  @endif

  {{-- #forecast --}}
  <x-fuel.forecast />

</main>

<x-mobile-footer />
```

---

## API Contracts

### `/api/stations`
| Param | Type | Notes |
|---|---|---|
| `postcode` | string | Full postcode, outcode, or place name |
| `fuel_type` | string | e.g. `petrol`, `diesel`, `e5` |
| `radius` | float | km (convert from miles: × 1.60934) |
| `sort` | string | `reliable`, `price`, `distance`, `updated`, `brand` |

Response shape: `{ data: Station[], meta: { count, lowest_pence, avg_pence } }`

### `/api/prediction`
| Param | Type | Notes |
|---|---|---|
| `lat` | float? | Optional — falls back to national |
| `lng` | float? | Optional |

Key response fields used: `action`, `confidence_score`, `confidence_label`, `reasoning`, `predicted_direction`, `predicted_change_pence`.

---

## Data Flow

```
User types postcode → submits → findStations()
  → GET /api/stations  → $results, $meta, $hasSearched=true
  → GET /api/prediction → $prediction
  → blade re-renders:
      #recommendation shows with prediction data
      #map re-centres to results
      #stations lists results
```

Filter change (fuel type / radius / sort) → `updated*()` → `findStations()` if `$hasSearched`

---

## Not in scope

- Desktop layout changes (StationSearch at `/stations` unchanged)
- Migrating StationSearch to use shared Blade components (follow-up)
- lat/lng resolution for `/api/prediction` (uses national fallback for now)
- Tab bar navigation routes (Alerts, Forecourts, Trends pages don't exist yet)
