diff --git a/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx b/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx
index 3ced31657..ef6236c50 100644
--- a/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx
+++ b/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx
@@ -348,6 +348,7 @@ export default function RoadtripCorridorPanel({ corridor, routes, onAddPoi }: Ro
scene={search.loading ? 'search' : 'idle'}
mood={filteredToNothing ? 'confused' : undefined}
size={88}
+ compact
surface="var(--bg-secondary)"
title={
search.loading
diff --git a/client/src/components/Roadtrip/RoadtripLimitsCard.tsx b/client/src/components/Roadtrip/RoadtripLimitsCard.tsx
index 6baeed246..f9c771f24 100644
--- a/client/src/components/Roadtrip/RoadtripLimitsCard.tsx
+++ b/client/src/components/Roadtrip/RoadtripLimitsCard.tsx
@@ -79,13 +79,16 @@ export default function RoadtripLimitsCard({ onSave }: {
onSave?.('roadtrip_range_km', Math.round(km))
}
- // What is set, on the button itself, so the form does not have to be open to see it.
- // Nothing set reads as "off" rather than as an empty string, which would look broken.
- const summary = [
- legMinutes ? formatDurationShort(legMinutes * 60) : null,
- dayMinutes ? formatDurationShort(dayMinutes * 60) : null,
- rangeKm ? formatDistance(rangeKm, distanceUnit) : null,
- ].filter(Boolean).join(' ยท ')
+ // What is set, on the button itself, so the form does not have to be open to read it.
+ // Each one keeps its own icon, because three numbers in a row say nothing about which
+ // is which: a clock, a calendar and a pump do. Distances go through formatDistance, so
+ // an imperial traveller reads miles here and types miles in the dialog, while what is
+ // stored stays kilometres either way.
+ const badges = [
+ legMinutes ? { key: 'leg', Icon: Clock, text: formatDurationShort(legMinutes * 60) } : null,
+ dayMinutes ? { key: 'day', Icon: CalendarClock, text: formatDurationShort(dayMinutes * 60) } : null,
+ rangeKm ? { key: 'range', Icon: Fuel, text: formatDistance(rangeKm, distanceUnit) } : null,
+ ].filter(Boolean) as { key: string; Icon: typeof Clock; text: string }[]
return (
<>
@@ -98,16 +101,37 @@ export default function RoadtripLimitsCard({ onSave }: {
onClick={() => setOpen(true)}
className="flex w-full items-center gap-3 rounded-xl border border-edge-faint bg-surface-card px-3.5 py-3 text-start transition-colors hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
>
-
-
-
-
+ {/* No icon tile on the trigger: the three badges below already carry a clock, a
+ calendar and a pump, and a fourth mark in front of the title only ate the width
+ they need to sit on one line. */}
+
{t('roadtrip.limit.title')}
-
- {summary || t('roadtrip.limit.none')}
-
+ {badges.length ? (
+
+ {badges.map(({ key, Icon, text }) => (
+
+
+
+
+
+ {text}
+
+
+ ))}
+
+ ) : (
+
+ {t('roadtrip.limit.none')}
+
+ )}
diff --git a/client/src/components/shared/EmptyState.tsx b/client/src/components/shared/EmptyState.tsx
index d1c54731c..89745ac79 100644
--- a/client/src/components/shared/EmptyState.tsx
+++ b/client/src/components/shared/EmptyState.tsx
@@ -22,6 +22,7 @@ export default function EmptyState({
size = 104,
surface = 'var(--bg-card)',
layout = 'stack',
+ compact = false,
className = '',
action,
}: {
@@ -31,6 +32,14 @@ export default function EmptyState({
size?: number
surface?: string
layout?: 'stack' | 'row'
+ /**
+ * Quieter type for a state inside a narrow column rather than on a page.
+ *
+ * The sidebar's empty state sits under its own controls, so a line at the page size
+ * competes with them for the eye; at the caption tier it reads as what it is, a note
+ * about why the list below is blank.
+ */
+ compact?: boolean
className?: string
/** Optional call to action under the title, for states that have an obvious next step. */
action?: ReactNode
@@ -44,7 +53,7 @@ export default function EmptyState({
style={{ '--m-ink': 'var(--text-primary)', '--m-bg': surface } as CSSProperties}
>
- {title}
+ {title}
{action}
)