diff --git a/code-style.md b/.claude/rules/code-style.md similarity index 100% rename from code-style.md rename to .claude/rules/code-style.md diff --git a/docs/theme.md b/docs/theme.md new file mode 100644 index 0000000..cddfd96 --- /dev/null +++ b/docs/theme.md @@ -0,0 +1,41 @@ +# FuelAlert — Colour Palette + +## Primary — Burnt Sienna +| Token | Hex | Usage | +|--------------|-----------|------------------------------| +| primary | `#bb5b3e` | CTA buttons, focus rings, brand | +| primary-dark | `#a34a31` | Hover / pressed state | + +## Neutrals — Warm Brown +| Token | Hex | Usage | +|-----------|-----------|------------------------------| +| text-base | `#4a3f3b` | Body text, headings | +| text-muted| `#89726c` | Secondary text, icons | +| text-dim | `#6b5a55` | Tertiary / placeholder text | + +## Surfaces — Cream / Linen +| Token | Hex | Usage | +|----------------|-----------|------------------------------| +| surface | `#faf6f3` | Input & card background | +| surface-page | `#f5ede5` | Page / app background | +| surface-subtle | `#eeeae5` | Subtle surface / hover | +| border | `#e5ded7` | Borders & dividers | + +## Accents +| Token | Hex | Usage | +|--------|-----------|------------------------------| +| teal | `#4A7C7E` | Secondary accent (minor) | +| mauve | `#8B4860` | Tertiary accent (minor) | +| tan | `#9B8B6B` | Warm neutral accent | + +## Status +| Token | Hex | Usage | +|---------|-----------|------------------------------| +| success | `#22c55e` | Price current / good signal | +| warning | `#f59e0b` | Stale price / weak signal | +| error | `#ef4444` | Outdated price / error state | + +## Notes +- Core brand feel: warm terracotta on a cream/linen base. +- Teal and mauve are currently used sparingly — confirm role in DaisyUI theme before promoting to named tokens. +- Never use cold grays — all neutrals should lean warm. diff --git a/resources/js/maps/station-map.js b/resources/js/maps/station-map.js index 2200675..9b335d4 100644 --- a/resources/js/maps/station-map.js +++ b/resources/js/maps/station-map.js @@ -26,15 +26,38 @@ const CLASSIFICATION_COLOURS = { }; const UK_CENTRE = [54.0, -2.0]; -const UK_ZOOM = 6; +const UK_ZOOM = 7; -export function stationMap(results) { +const USER_MARKER_CSS = ` +@keyframes fuelalert-pulse { + 0% { transform: scale(1); opacity: 0.6; } + 70% { transform: scale(2.8); opacity: 0; } + 100% { transform: scale(1); opacity: 0; } +} +.fuelalert-user-marker { position: relative; width: 16px; height: 16px; } +.fuelalert-user-dot { position: absolute; inset: 0; border-radius: 50%; background: #3b82f6; border: 2px solid #fff; box-shadow: 0 0 0 2px #3b82f6; } +.fuelalert-user-ring { position: absolute; inset: 0; border-radius: 50%; background: #3b82f6; animation: fuelalert-pulse 2s ease-out infinite; } +`; + +function injectUserMarkerStyles() { + if (document.getElementById('fuelalert-user-marker-styles')) return; + const style = document.createElement('style'); + style.id = 'fuelalert-user-marker-styles'; + style.textContent = USER_MARKER_CSS; + document.head.appendChild(style); +} + +export function stationMap(results, meta, radius) { return { results, + meta, + radius, _map: null, _markers: [], + _userMarker: null, init() { + injectUserMarkerStyles(); this._map = L.map(this.$el, { zoomControl: true }).setView(UK_CENTRE, UK_ZOOM); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { @@ -47,6 +70,18 @@ export function stationMap(results) { } this.$watch('results', () => this._plotMarkers()); + this.locateUser(); + }, + + getZoomForRadius(radiusMiles) { + if (radiusMiles <= 1) return 15; + if (radiusMiles <= 2) return 14; + if (radiusMiles <= 5) return 12; + if (radiusMiles <= 10) return 11; + if (radiusMiles <= 15) return 10; + if (radiusMiles <= 25) return 9; + if (radiusMiles <= 50) return 8; + return 7; }, _clearMarkers() { @@ -54,11 +89,61 @@ export function stationMap(results) { this._markers = []; }, + addUserMarker(lat, lng) { + if (this._userMarker) { + this._userMarker.remove(); + } + + const icon = L.divIcon({ + className: '', + html: '