- Increase temp size to 2rem and add explicit font-size to condition - Uniform 0.5rem spacing below current temp (was 0.35rem) - Today row font-size explicit at 0.85rem - Forecast gap 0.75rem (was 0.5rem), min-width 4.5rem (was 3.5rem) - Padding-top on forecast strip 0.75rem to match overall rhythm Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
171 lines
3.8 KiB
Vue
171 lines
3.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
|
|
interface ForecastDay {
|
|
day: string
|
|
condition: string
|
|
high: number
|
|
low: number
|
|
}
|
|
|
|
interface WeatherData {
|
|
location: string
|
|
fetched_at: string
|
|
current_temp: number
|
|
condition: string
|
|
today_high: number | null
|
|
today_low: number | null
|
|
yesterday_high: number | null
|
|
yesterday_low: number | null
|
|
forecast: ForecastDay[]
|
|
}
|
|
|
|
const props = defineProps<{
|
|
weather: WeatherData | null
|
|
tempUnit?: string
|
|
}>()
|
|
|
|
const unit = computed(() => props.tempUnit ?? 'C')
|
|
|
|
const tempDelta = computed(() => {
|
|
const w = props.weather
|
|
if (!w || w.today_high == null || w.yesterday_high == null) return null
|
|
const diff = w.today_high - w.yesterday_high
|
|
if (Math.abs(diff) < 1) return 'Same as yesterday'
|
|
const dir = diff > 0 ? 'warmer' : 'cooler'
|
|
return `${Math.abs(diff)}° ${dir} than yesterday`
|
|
})
|
|
|
|
const fetchedAtLabel = computed(() => {
|
|
if (!props.weather?.fetched_at) return ''
|
|
try {
|
|
return new Date(props.weather.fetched_at).toLocaleTimeString([], {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})
|
|
} catch {
|
|
return ''
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="weather" class="weather-card">
|
|
<div class="weather-header">
|
|
<span class="weather-location">{{ weather.location }}</span>
|
|
<span class="weather-fetched-at">as of {{ fetchedAtLabel }}</span>
|
|
</div>
|
|
<div class="weather-current">
|
|
<span class="weather-temp">{{ weather.current_temp }}°{{ unit }}</span>
|
|
<span class="weather-condition">{{ weather.condition }}</span>
|
|
</div>
|
|
<div class="weather-today" v-if="weather.today_high != null">
|
|
Today: {{ weather.today_high }}° / {{ weather.today_low }}°
|
|
<span v-if="tempDelta" class="weather-delta"> · {{ tempDelta }}</span>
|
|
</div>
|
|
<div class="weather-forecast" v-if="weather.forecast.length">
|
|
<div v-for="day in weather.forecast" :key="day.day" class="weather-forecast-day">
|
|
<span class="forecast-day-name">{{ day.day }}</span>
|
|
<span class="forecast-condition">{{ day.condition }}</span>
|
|
<span class="forecast-temps">{{ day.high }}° / {{ day.low }}°</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="weather-card weather-unavailable">
|
|
Weather data unavailable — will retry at next slot.
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.weather-card {
|
|
background: color-mix(in srgb, var(--color-surface) 80%, transparent);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 1rem 1.25rem;
|
|
margin-bottom: 1rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.weather-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: baseline;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.weather-location {
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.weather-fetched-at {
|
|
color: var(--color-text-muted);
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
.weather-current {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 0.75rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.weather-temp {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
}
|
|
|
|
.weather-condition {
|
|
color: var(--color-text-muted);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.weather-today {
|
|
color: var(--color-text-secondary);
|
|
margin-bottom: 0.75rem;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.weather-delta {
|
|
color: var(--color-text-muted);
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.weather-forecast {
|
|
display: flex;
|
|
gap: 0.75rem;
|
|
overflow-x: auto;
|
|
padding-top: 0.75rem;
|
|
border-top: 1px solid var(--color-border);
|
|
}
|
|
|
|
.weather-forecast-day {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
min-width: 4.5rem;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.forecast-day-name {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.forecast-condition {
|
|
color: var(--color-text-muted);
|
|
font-size: 0.75rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.forecast-temps {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.weather-unavailable {
|
|
color: var(--color-text-muted);
|
|
font-style: italic;
|
|
}
|
|
</style>
|