Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
690d04f96b | ||
|
|
f2371ec5ce | ||
|
|
6ede9b5620 | ||
|
|
6ffef099cd | ||
|
|
f0eb425ec6 | ||
|
|
793918eeff | ||
|
|
ba9834a3eb | ||
|
|
298c8ef597 |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"userWords": ["ConfigInterface"]
|
||||
}
|
||||
Generated
+1
@@ -8,6 +8,7 @@
|
||||
"name": "open-meteo-weather",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@openmeteo/sdk": "^1.23.0",
|
||||
"highcharts": "^12.4.0",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"openmeteo": "^1.2.3"
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"vitest-browser-svelte": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openmeteo/sdk": "^1.23.0",
|
||||
"highcharts": "^12.4.0",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"openmeteo": "^1.2.3"
|
||||
|
||||
@@ -242,8 +242,8 @@
|
||||
{location.country || ''}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{location.latitude.toFixed(2)}°N {location.longitude.toFixed(2)}°E
|
||||
{#if location.elevation}• {location.elevation.toFixed(0)}m{/if}
|
||||
{location.latitude?.toFixed(2)}°N {location.longitude?.toFixed(2)}°E
|
||||
{#if location.elevation}• {location.elevation?.toFixed(0)}m{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+18
-18
@@ -1,24 +1,24 @@
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
|
||||
export interface GeoLocation {
|
||||
id: number;
|
||||
name: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
elevation: number;
|
||||
feature_code: string;
|
||||
country_code: string | undefined;
|
||||
admin1_id: number | undefined;
|
||||
admin3_id?: number | undefined;
|
||||
admin4_id?: number | undefined;
|
||||
timezone: string;
|
||||
population: number | undefined;
|
||||
postcodes: string[] | undefined;
|
||||
country_id: number | undefined;
|
||||
country: string | undefined;
|
||||
admin1: string | undefined;
|
||||
admin3?: string | undefined;
|
||||
admin4?: string | undefined;
|
||||
id?: number;
|
||||
name?: string;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
elevation?: number;
|
||||
feature_code?: string;
|
||||
country_code?: string;
|
||||
admin1_id?: number;
|
||||
admin3_id?: number;
|
||||
admin4_id?: number;
|
||||
timezone?: string;
|
||||
population?: number;
|
||||
postcodes?: string[];
|
||||
country_id?: number;
|
||||
country?: string;
|
||||
admin1?: string;
|
||||
admin3?: string;
|
||||
admin4?: string;
|
||||
}
|
||||
|
||||
export const defaultLocation: GeoLocation = {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
export interface Parameters {
|
||||
latitude?: number | number[];
|
||||
longitude?: number | number[];
|
||||
hourly?: string[];
|
||||
models?: string[];
|
||||
daily?: string[];
|
||||
current?: string[];
|
||||
minutely_15?: string[];
|
||||
timezone?: string;
|
||||
location_mode?: string;
|
||||
csv_coordinates?: string;
|
||||
time_mode?: string;
|
||||
past_days?: string;
|
||||
forecast_days?: string;
|
||||
end_date?: string;
|
||||
start_date?: string;
|
||||
past_hours?: string;
|
||||
cell_selection?: string;
|
||||
forecast_hours?: string;
|
||||
past_minutely_15?: string;
|
||||
temporal_resolution?: string;
|
||||
forecast_minutely_15?: string;
|
||||
tilt?: string;
|
||||
azimuth?: string;
|
||||
timeformat?: string;
|
||||
wind_speed_unit?: string;
|
||||
temperature_unit?: string;
|
||||
precipitation_unit?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
@@ -1,9 +1,26 @@
|
||||
export * from './ui.ts';
|
||||
export * from './meteo.ts';
|
||||
|
||||
export const isNumeric = (num: string | number) =>
|
||||
(typeof num === 'number' || (typeof num === 'string' && num.trim() !== '')) &&
|
||||
!isNaN(num as number);
|
||||
|
||||
export const pad = (n: string | number) => {
|
||||
if (n === null || n === undefined) {
|
||||
return '';
|
||||
}
|
||||
return ('0' + n).slice(-2);
|
||||
};
|
||||
|
||||
export function debounce<F extends (...args: unknown[]) => unknown>(
|
||||
func: F,
|
||||
timeout = 100
|
||||
): (this: ThisParameterType<F>, ...args: Parameters<F>) => void {
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
return function (this: ThisParameterType<F>, ...args: Parameters<F>) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, timeout);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const prerender = true;
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
|
||||
import { storedLocation } from '$lib/stores/settings';
|
||||
@@ -15,7 +14,7 @@
|
||||
} from '$lib/components/ui/card';
|
||||
|
||||
let mounted = $state(false);
|
||||
let location = get(storedLocation);
|
||||
let location = $derived($storedLocation);
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
@@ -182,7 +181,9 @@
|
||||
Current Location: {location.name}
|
||||
</CardTitle>
|
||||
<CardDescription class="text-center">
|
||||
{location.country} • {location.latitude.toFixed(2)}°, {location.longitude.toFixed(2)}°
|
||||
{location.country} • {location.latitude?.toFixed(2)}°, {location.longitude?.toFixed(
|
||||
2
|
||||
)}°
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="text-center">
|
||||
|
||||
@@ -134,8 +134,8 @@
|
||||
{/if}{location.country}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{location.latitude.toFixed(2)}°N, {location.longitude.toFixed(2)}°E
|
||||
{#if location.elevation}• {location.elevation.toFixed(0)}m{/if}
|
||||
{location.latitude?.toFixed(2)}°N, {location.longitude?.toFixed(2)}°E
|
||||
{#if location.elevation}• {location.elevation?.toFixed(0)}m{/if}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { get } from 'svelte/store';
|
||||
|
||||
import { storedLocation } from '$lib/stores/settings';
|
||||
|
||||
import type { LayoutLoad } from './$types';
|
||||
import type { LayoutLoad } from '././$types';
|
||||
|
||||
const location = get(storedLocation);
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ import type { PageLoad } from './$types';
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const load = (async () => {
|
||||
export const load: PageLoad = async () => {
|
||||
throw redirect(303, '/weather/week/');
|
||||
}) satisfies PageLoad;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { get } from 'svelte/store';
|
||||
|
||||
import { storedLocation } from '$lib/stores/settings';
|
||||
|
||||
import type { LayoutLoad } from './$types';
|
||||
import type { LayoutLoad } from '././$types';
|
||||
|
||||
const location = get(storedLocation);
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { ConfigInterface } from '../config';
|
||||
|
||||
export default (
|
||||
ctx: CanvasRenderingContext2D | null | undefined,
|
||||
config: ConfigInterface,
|
||||
series: Float32Array | null | undefined,
|
||||
canvasElement: HTMLCanvasElement
|
||||
series: Float32Array | null | undefined
|
||||
): void => {
|
||||
if (ctx && series) {
|
||||
const fillColor = `hsla(${config.styles.mutedForeground}, 0.5)`;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 35 + (series[0] ** 1.5 / 1000) * 30);
|
||||
for (const [index, value] of series.entries()) {
|
||||
@@ -71,8 +74,8 @@ export default (
|
||||
);
|
||||
|
||||
ctx.closePath();
|
||||
//to fill the space in the shape
|
||||
ctx.fillStyle = `hsla(${getComputedStyle(canvasElement).getPropertyValue('--muted-foreground').split(' ').join(',')}, 0.5)`;
|
||||
// USE PRE-CALC STYLE
|
||||
ctx.fillStyle = fillColor;
|
||||
ctx.fill();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ConfigInterface } from '../config';
|
||||
|
||||
export default (
|
||||
ctx: CanvasRenderingContext2D | null | undefined,
|
||||
config: ConfigInterface,
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import type { ConfigInterface } from '../config';
|
||||
|
||||
export default (
|
||||
ctx: CanvasRenderingContext2D | null | undefined,
|
||||
config: ConfigInterface,
|
||||
series: Float32Array | null | undefined,
|
||||
canvasElement: HTMLCanvasElement
|
||||
series: Float32Array | null | undefined
|
||||
): void => {
|
||||
if (ctx && series) {
|
||||
const strokeStyle = `hsla(${config.styles.primary}, 1)`;
|
||||
|
||||
for (const [index, value] of series.entries()) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(index * config.deltaX + 0.5 * config.deltaX, config.maxY);
|
||||
|
||||
ctx.strokeStyle = `hsla(${getComputedStyle(canvasElement).getPropertyValue('--primary').split(' ').join(',')}, 1)`;
|
||||
ctx.strokeStyle = strokeStyle;
|
||||
ctx.lineWidth = 12;
|
||||
|
||||
ctx.lineTo(index * config.deltaX + 0.5 * config.deltaX, config.maxY - value ** 0.55 * 45);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ConfigInterface } from '../config';
|
||||
|
||||
export default (
|
||||
ctx: CanvasRenderingContext2D | null | undefined,
|
||||
config: ConfigInterface,
|
||||
@@ -18,7 +20,6 @@ export default (
|
||||
series[index].getHours() === today.getHours()
|
||||
) {
|
||||
// fill now line
|
||||
// TODO: update this line every minute
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
ctx.beginPath();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { getColor } from '../utils/colors';
|
||||
|
||||
import type { ConfigInterface } from '../config';
|
||||
|
||||
export default (
|
||||
ctx: CanvasRenderingContext2D | null | undefined,
|
||||
config: ConfigInterface,
|
||||
@@ -8,10 +10,10 @@ export default (
|
||||
): void => {
|
||||
if (ctx && series) {
|
||||
const tempGradientFill = ctx.createLinearGradient(0, 0, 0, config.maxY);
|
||||
tempGradientFill.addColorStop(0, getColor(config.maxTemp.toFixed(0), unit) + '5c');
|
||||
tempGradientFill.addColorStop(0.25, getColor(config.maxTemp.toFixed(0), unit) + '5c');
|
||||
tempGradientFill.addColorStop(0.85, getColor(config.minTemp.toFixed(0), unit) + '06');
|
||||
tempGradientFill.addColorStop(1, getColor(config.minTemp.toFixed(0), unit) + '00');
|
||||
tempGradientFill.addColorStop(0, getColor(config.maxTemp, unit) + '5c');
|
||||
tempGradientFill.addColorStop(0.25, getColor(config.maxTemp, unit) + '5c');
|
||||
tempGradientFill.addColorStop(0.85, getColor(config.minTemp, unit) + '06');
|
||||
tempGradientFill.addColorStop(1, getColor(config.minTemp, unit) + '00');
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
|
||||
@@ -2,7 +2,7 @@ import { get } from 'svelte/store';
|
||||
|
||||
import { storedLocation } from '$lib/stores/settings';
|
||||
|
||||
import type { LayoutLoad } from './$types';
|
||||
import type { LayoutLoad } from '././$types';
|
||||
|
||||
const location = get(storedLocation);
|
||||
|
||||
|
||||
@@ -11,13 +11,10 @@
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
|
||||
import { hourly, models as modelsFlat } from '../options';
|
||||
import { hourly, models } from '../options';
|
||||
import './highcharts.css';
|
||||
import { defaultParameters } from './options';
|
||||
|
||||
// Wrap models in array to match template expectation of nested arrays like hourly
|
||||
const models = [modelsFlat];
|
||||
|
||||
let node: HTMLElement;
|
||||
let chart: any;
|
||||
let Highcharts = $state<typeof import('highcharts') | null>(null);
|
||||
@@ -338,44 +335,41 @@
|
||||
<div
|
||||
class="absolute -top-1 ml-2 rounded-full border-2 border-foreground/25 bg-secondary px-3 py-1 text-sm no-underline"
|
||||
>
|
||||
{params.models?.length || 0} / {models.flat().length}
|
||||
{params.models?.length || 0} / {models.length}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-2 grid sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
|
||||
{#each models as group, i (i)}
|
||||
<div class="mb-3">
|
||||
{#each group as item (item.value)}
|
||||
{@const { value, label } = item as { value: string; label: string }}
|
||||
<div class="group flex items-center" title={label}>
|
||||
<Checkbox
|
||||
id="{value}_model"
|
||||
class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
|
||||
{value}
|
||||
checked={params.models?.includes(value)}
|
||||
aria-labelledby="{value}_label"
|
||||
onCheckedChange={() => {
|
||||
if (params.models?.includes(value)) {
|
||||
params.models = params.models.filter((item) => {
|
||||
return item !== value;
|
||||
});
|
||||
} else if (params.models) {
|
||||
params.models.push(value);
|
||||
params.models = params.models;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
id="{value}_model_label"
|
||||
for="{value}_model"
|
||||
class="cursor-pointer truncate py-[0.1rem] pl-[0.42rem]">{label}</Label
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
<div class="mb-3">
|
||||
{#each models as { value, label } (value)}
|
||||
<div class="group flex items-center" title={label}>
|
||||
<Checkbox
|
||||
id="{value}_model"
|
||||
class="border-border-dark cursor-pointer bg-muted/50 duration-100 group-hover:border-[currentColor]"
|
||||
{value}
|
||||
checked={params.models?.includes(value)}
|
||||
aria-labelledby="{value}_label"
|
||||
onCheckedChange={() => {
|
||||
if (params.models?.includes(value)) {
|
||||
params.models = params.models.filter((item) => {
|
||||
return item !== value;
|
||||
});
|
||||
} else if (params.models) {
|
||||
params.models.push(value);
|
||||
params.models = params.models;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
id="{value}_model_label"
|
||||
for="{value}_model"
|
||||
class="cursor-pointer truncate py-[0.1rem] pl-[0.42rem]">{label}</Label
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HOURLY -->
|
||||
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
interface ConfigInterface {
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
deltaX: number;
|
||||
minTemp: number;
|
||||
maxTemp: number;
|
||||
diffTemp: number;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export interface ConfigInterface {
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
deltaX: number;
|
||||
minTemp: number;
|
||||
maxTemp: number;
|
||||
diffTemp: number;
|
||||
styles: {
|
||||
mutedForeground: string;
|
||||
primary: string;
|
||||
border: string;
|
||||
};
|
||||
}
|
||||
@@ -25,19 +25,18 @@ export function rgbToHex(rgb: string) {
|
||||
return hex;
|
||||
}
|
||||
|
||||
export const getColor = (tempString: string, unit = 'celsius'): string => {
|
||||
export const getColor = (value: number, unit = 'celsius'): string => {
|
||||
let index = 0;
|
||||
const temp = Number(tempString);
|
||||
if (unit === 'celsius') {
|
||||
if (temp <= -40) {
|
||||
if (value <= -40) {
|
||||
index = 0;
|
||||
} else if (temp >= 60) {
|
||||
} else if (value >= 60) {
|
||||
index = colorScaleHex.length - 1;
|
||||
} else {
|
||||
index = temp + 40;
|
||||
index = value + 40;
|
||||
}
|
||||
} else {
|
||||
const tempInCelsius = Math.round(((temp - 32) * 5) / 9);
|
||||
const tempInCelsius = Math.round(((value - 32) * 5) / 9);
|
||||
if (tempInCelsius <= -40) {
|
||||
index = 0;
|
||||
} else if (tempInCelsius >= 60) {
|
||||
@@ -47,5 +46,34 @@ export const getColor = (tempString: string, unit = 'celsius'): string => {
|
||||
}
|
||||
}
|
||||
|
||||
index = Math.floor(index);
|
||||
|
||||
return colorScaleHex[index];
|
||||
};
|
||||
|
||||
export const textWhite = (hex: string): boolean => {
|
||||
const cleaned = (hex || '').replace('#', '').trim().toLowerCase();
|
||||
if (!cleaned) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
|
||||
if (cleaned.length === 6) {
|
||||
r = parseInt(cleaned.slice(0, 2), 16);
|
||||
g = parseInt(cleaned.slice(2, 4), 16);
|
||||
b = parseInt(cleaned.slice(4, 6), 16);
|
||||
} else {
|
||||
throw new Error('Invalid color format');
|
||||
}
|
||||
|
||||
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Perceived brightness (YIQ / luma). If brightness is low, use white text.
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
return brightness < 128;
|
||||
};
|
||||
|
||||
@@ -10,9 +10,9 @@ import type { PageLoad } from './$types';
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
export const load = (async () => {
|
||||
export const load: PageLoad = async () => {
|
||||
const location = get(storedLocation);
|
||||
const locationRoute = geoLocationNameToRoute(location.name);
|
||||
const locationRoute = geoLocationNameToRoute(location.name || '');
|
||||
throw redirect(
|
||||
303,
|
||||
'/weather/week/' +
|
||||
@@ -22,4 +22,4 @@ export const load = (async () => {
|
||||
: locationRoute + '_' + location.id
|
||||
: locationRoute + '_' + location.id)
|
||||
);
|
||||
}) satisfies PageLoad;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
import { getColor } from '../../utils/colors';
|
||||
import weatherCodes from '../../utils/weather-codes';
|
||||
|
||||
import type { ConfigInterface } from '../../config';
|
||||
|
||||
let params = $state({
|
||||
latitude: [$storedLocation.latitude],
|
||||
longitude: [$storedLocation.longitude],
|
||||
@@ -113,15 +115,20 @@
|
||||
deltaX: deltaX,
|
||||
minTemp: minTemp,
|
||||
maxTemp: maxTemp,
|
||||
diffTemp: diffTemp
|
||||
diffTemp: diffTemp,
|
||||
styles: {
|
||||
mutedForeground: '240 3.7% 15.9%',
|
||||
primary: '222.2 47.4% 11.2%',
|
||||
border: '214.3 31.8% 91.4%'
|
||||
}
|
||||
};
|
||||
|
||||
// create canvas
|
||||
daylight(ctx, config, hourlyTime);
|
||||
raster(ctx, config, hourlyTime, today, canvasElement!);
|
||||
tempGradient(ctx, config, hourlyTemps, params.temperature_unit);
|
||||
cloudCover(ctx, config, hourlyCloudCover, canvasElement!);
|
||||
precip(ctx, config, hourlyPrecip, canvasElement!);
|
||||
cloudCover(ctx, config, hourlyCloudCover);
|
||||
precip(ctx, config, hourlyPrecip);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -348,14 +355,14 @@
|
||||
</div>
|
||||
<div
|
||||
class="weather-temp-max flex min-w-[65px] justify-center rounded-t p-1 text-sm"
|
||||
style={`background-color: ${getColor((wd.daily.temperature_2m_max.values(index) ?? 0).toFixed(0), params.temperature_unit)}; color: ${(wd.daily.temperature_2m_min.values(index) ?? 0) < (params.temperature_unit === 'celsius' ? 4 : 7) || (wd.daily.temperature_2m_min.values(index) ?? 0) >= (params.temperature_unit === 'celsius' ? 30 : 104) ? 'white' : 'black'}`}
|
||||
style={`background-color: ${getColor(Math.round(wd.daily.temperature_2m_max.values(index) ?? 0), params.temperature_unit)}; color: ${(wd.daily.temperature_2m_min.values(index) ?? 0) < (params.temperature_unit === 'celsius' ? 4 : 7) || (wd.daily.temperature_2m_min.values(index) ?? 0) >= (params.temperature_unit === 'celsius' ? 30 : 104) ? 'white' : 'black'}`}
|
||||
>
|
||||
{wd.daily.temperature_2m_max.values(index)?.toFixed(1)}
|
||||
{params.temperature_unit === 'celsius' ? '°C' : '°F'}
|
||||
</div>
|
||||
<div
|
||||
class="weather-temp-min flex min-w-[65px] justify-center rounded-b p-1 text-sm"
|
||||
style={`background: ${getColor((wd.daily.temperature_2m_min.values(index) ?? 0).toFixed(0), params.temperature_unit)}; color: ${(wd.daily.temperature_2m_min.values(index) ?? 0) < (params.temperature_unit === 'celsius' ? 4 : 7) || (wd.daily.temperature_2m_min.values(index) ?? 0) >= (params.temperature_unit === 'celsius' ? 30 : 104) ? 'white' : 'black'}`}
|
||||
style={`background: ${getColor(Math.round(wd.daily.temperature_2m_min.values(index) ?? 0), params.temperature_unit)}; color: ${(wd.daily.temperature_2m_min.values(index) ?? 0) < (params.temperature_unit === 'celsius' ? 4 : 7) || (wd.daily.temperature_2m_min.values(index) ?? 0) >= (params.temperature_unit === 'celsius' ? 30 : 104) ? 'white' : 'black'}`}
|
||||
>
|
||||
{wd.daily.temperature_2m_min.values(index)?.toFixed(1)}
|
||||
{params.temperature_unit === 'celsius' ? '°C' : '°F'}
|
||||
@@ -536,7 +543,7 @@
|
||||
{entry.name === 'temperature_2m'
|
||||
? 'background: ' +
|
||||
getColor(
|
||||
weather.entries[0].values![index].toFixed(0),
|
||||
Math.round(weather.entries[0].values![index]),
|
||||
params.temperature_unit
|
||||
)
|
||||
: ''};
|
||||
|
||||
@@ -72,7 +72,7 @@ export const load: PageLoad = async (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const locationRoute = geoLocationNameToRoute(location.name);
|
||||
const locationRoute = geoLocationNameToRoute(location.name || '');
|
||||
|
||||
if (location.population && location.population > 543000) {
|
||||
// 1000 biggest cities
|
||||
|
||||
+5
-1
@@ -7,7 +7,11 @@ const config = {
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: { adapter: adapter() }
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
fallback: '404.html'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
Reference in New Issue
Block a user