about page

This commit is contained in:
vcoppe
2024-07-05 16:08:16 +02:00
parent 9edcc5b55b
commit cb735db216
27 changed files with 44025 additions and 167 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -12,15 +12,30 @@
import { Toaster } from '$lib/components/ui/sonner';
import { settings } from '$lib/db';
import { gpxStatistics, slicedGPXStatistics } from '$lib/stores';
const { verticalFileView, elevationProfile, bottomPanelSize, rightPanelSize } = settings;
const {
verticalFileView,
elevationProfile,
bottomPanelSize,
rightPanelSize,
distanceUnits,
velocityUnits,
temperatureUnits,
additionalDatasets,
elevationFill
} = settings;
</script>
<div class="fixed flex flex-row w-screen h-screen h-dvh">
<div class="flex flex-col grow h-full min-w-0">
<div class="grow relative">
<Menu />
<Toolbar />
<div
class="absolute top-0 bottom-0 left-0 z-20 flex flex-col justify-center pointer-events-none"
>
<Toolbar />
</div>
<Map class="h-full {$verticalFileView ? '' : 'horizontal'}" />
<StreetViewControl />
<LayerControl />
@@ -39,9 +54,25 @@
class="{$elevationProfile ? '' : 'h-10'} flex flex-row gap-2"
style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''}
>
<GPXStatistics />
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={$bottomPanelSize}
orientation={$elevationProfile ? 'vertical' : 'horizontal'}
velocityUnits={$velocityUnits}
/>
{#if $elevationProfile}
<ElevationProfile />
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
bind:additionalDatasets={$additionalDatasets}
bind:elevationFill={$elevationFill}
panelSize={$bottomPanelSize}
distanceUnits={$distanceUnits}
velocityUnits={$velocityUnits}
temperatureUnits={$temperatureUnits}
class="py-2 pr-4"
/>
{/if}
</div>
</div>

View File

@@ -3,8 +3,7 @@
import Tooltip from '$lib/components/Tooltip.svelte';
import Chart from 'chart.js/auto';
import mapboxgl from 'mapbox-gl';
import { map, gpxStatistics, slicedGPXStatistics } from '$lib/stores';
import { settings } from '$lib/db';
import { map } from '$lib/stores';
import { onDestroy, onMount } from 'svelte';
import {
BrickWall,
@@ -37,8 +36,18 @@
getVelocityWithUnits,
secondsToHHMMSS
} from '$lib/units';
import { get } from 'svelte/store';
import type { Writable } from 'svelte/store';
import { DateFormatter } from '@internationalized/date';
import type { GPXStatistics } from 'gpx';
export let gpxStatistics: Writable<GPXStatistics>;
export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
export let distanceUnits: 'metric' | 'imperial';
export let velocityUnits: 'speed' | 'pace';
export let temperatureUnits: 'celsius' | 'fahrenheit';
export let panelSize: number;
export let additionalDatasets: string[];
export let elevationFill: 'slope' | 'surface' | undefined;
let df: DateFormatter;
@@ -50,20 +59,19 @@
}
let canvas: HTMLCanvasElement;
let showAdditionalScales = true;
let updateShowAdditionalScales = () => {
showAdditionalScales = canvas.width >= 1200;
};
let overlay: HTMLCanvasElement;
let chart: Chart;
Chart.defaults.font.family =
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font
let elevationFill: string;
let additionalDatasets: string[];
let marker: mapboxgl.Marker | null = null;
let dragging = false;
let { distanceUnits, velocityUnits, temperatureUnits, bottomPanelSize } = settings;
let options = {
animation: false,
parsing: false,
@@ -127,7 +135,7 @@
}
return `${$_('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`;
} else if (context.datasetIndex === 1) {
return `${$velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')}: ${getVelocityWithUnits(point.y, false)}`;
return `${velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')}: ${getVelocityWithUnits(point.y, false)}`;
} else if (context.datasetIndex === 2) {
return `${$_('quantities.heartrate')}: ${getHeartRateWithUnits(point.y)}`;
} else if (context.datasetIndex === 3) {
@@ -173,6 +181,7 @@
onResize: function (chart, size) {
overlay.width = size.width;
overlay.height = size.height;
updateShowAdditionalScales();
}
};
@@ -185,7 +194,7 @@
} = {
speed: {
id: 'speed',
getLabel: () => ($velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')),
getLabel: () => (velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')),
getUnits: () => getVelocityUnits()
},
hr: {
@@ -225,13 +234,13 @@
grid: {
display: false
},
reverse: () => id === 'speed' && $velocityUnits === 'pace',
reverse: () => id === 'speed' && velocityUnits === 'pace',
display: false
};
}
options.scales.yspeed['ticks'] = {
callback: function (value: number) {
if ($velocityUnits === 'speed') {
if (velocityUnits === 'speed') {
return value;
} else {
return secondsToHHMMSS(value);
@@ -268,6 +277,8 @@
element
});
updateShowAdditionalScales();
// Overlay canvas to create a selection rectangle
overlay.width = canvas.width;
overlay.height = canvas.height;
@@ -288,7 +299,7 @@
if (points.length === 0) {
return evt.x - rect.left <= chart.chartArea.left
? 0
: get(gpxStatistics).local.points.length - 1;
: $gpxStatistics.local.points.length - 1;
}
let point = points.find((point) => point.element.raw);
if (point) {
@@ -309,14 +320,11 @@
dragging = true;
endIndex = getIndex(evt);
if (startIndex !== endIndex) {
slicedGPXStatistics.set([
get(gpxStatistics).slice(
Math.min(startIndex, endIndex),
Math.max(startIndex, endIndex)
),
$slicedGPXStatistics = [
$gpxStatistics.slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex)),
Math.min(startIndex, endIndex),
Math.max(startIndex, endIndex)
]);
];
}
}
}
@@ -326,7 +334,7 @@
canvas.style.cursor = '';
endIndex = getIndex(evt);
if (startIndex === endIndex) {
slicedGPXStatistics.set(undefined);
$slicedGPXStatistics = undefined;
}
}
canvas.addEventListener('pointerdown', onMouseDown);
@@ -334,7 +342,7 @@
canvas.addEventListener('pointerup', onMouseUp);
});
$: if (chart && $distanceUnits && $velocityUnits && $temperatureUnits) {
$: if (chart && distanceUnits && velocityUnits && temperatureUnits) {
let data = $gpxStatistics;
// update data
@@ -488,11 +496,12 @@
chart.data.datasets[4].hidden = !includeTemperature;
chart.data.datasets[5].hidden = !includePower;
}
chart.options.scales[`y${datasets.speed.id}`].display = includeSpeed;
chart.options.scales[`y${datasets.hr.id}`].display = includeHeartRate;
chart.options.scales[`y${datasets.cad.id}`].display = includeCadence;
chart.options.scales[`y${datasets.atemp.id}`].display = includeTemperature;
chart.options.scales[`y${datasets.power.id}`].display = includePower;
chart.options.scales[`y${datasets.speed.id}`].display = includeSpeed && showAdditionalScales;
chart.options.scales[`y${datasets.hr.id}`].display = includeHeartRate && showAdditionalScales;
chart.options.scales[`y${datasets.cad.id}`].display = includeCadence && showAdditionalScales;
chart.options.scales[`y${datasets.atemp.id}`].display =
includeTemperature && showAdditionalScales;
chart.options.scales[`y${datasets.power.id}`].display = includePower && showAdditionalScales;
chart.update();
}
@@ -507,10 +516,10 @@
selectionContext.clearRect(0, 0, overlay.width, overlay.height);
let startPixel = chart.scales.x.getPixelForValue(
getConvertedDistance(get(gpxStatistics).local.distance.total[startIndex])
getConvertedDistance($gpxStatistics.local.distance.total[startIndex])
);
let endPixel = chart.scales.x.getPixelForValue(
getConvertedDistance(get(gpxStatistics).local.distance.total[endIndex])
getConvertedDistance($gpxStatistics.local.distance.total[endIndex])
);
selectionContext.fillRect(
@@ -534,17 +543,14 @@
});
</script>
<div class="h-full grow min-w-0 flex flex-row gap-4 items-center py-2 pr-4">
<div class="h-full grow min-w-0 flex flex-row gap-4 items-center {$$props.class ?? ''}">
<div class="grow h-full min-w-0">
<canvas bind:this={overlay} class="absolute pointer-events-none"></canvas>
<canvas bind:this={canvas} class="w-full h-full"></canvas>
</div>
<div
class="h-full flex flex-col justify-center"
style="width: {$bottomPanelSize > 158 ? 22 : 42}px"
>
<div class="h-full flex flex-col justify-center" style="width: {panelSize > 158 ? 22 : 42}px">
<ToggleGroup.Root
class="{$bottomPanelSize > 158
class="{panelSize > 158
? 'flex-col'
: 'flex-row'} flex-wrap gap-0 min-h-0 content-center border rounded-t-md"
type="single"
@@ -564,7 +570,7 @@
</ToggleGroup.Item>
</ToggleGroup.Root>
<ToggleGroup.Root
class="{$bottomPanelSize > 158
class="{panelSize > 158
? 'flex-col'
: 'flex-row'} flex-wrap gap-0 min-h-0 content-center border rounded-b-md -mt-[1px]"
type="multiple"
@@ -574,7 +580,7 @@
<Tooltip side="left">
<Zap slot="data" size="15" />
<span slot="tooltip"
>{$velocityUnits === 'speed' ? $_('chart.show_speed') : $_('chart.show_pace')}</span
>{velocityUnits === 'speed' ? $_('chart.show_speed') : $_('chart.show_pace')}</span
>
</Tooltip>
</ToggleGroup.Item>

View File

@@ -3,15 +3,17 @@
import Tooltip from '$lib/components/Tooltip.svelte';
import WithUnits from '$lib/components/WithUnits.svelte';
import { gpxStatistics, slicedGPXStatistics } from '$lib/stores';
import { settings } from '$lib/db';
import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import type { GPXStatistics } from 'gpx';
import type { Writable } from 'svelte/store';
const { velocityUnits, elevationProfile, bottomPanelSize } = settings;
export let gpxStatistics: Writable<GPXStatistics>;
export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
export let velocityUnits: 'speed' | 'pace';
export let orientation: 'horizontal' | 'vertical';
export let panelSize: number;
let statistics: GPXStatistics;
@@ -23,14 +25,14 @@
</script>
<Card.Root
class="h-full {$elevationProfile
? ''
: 'w-full pr-4'} overflow-hidden border-none shadow-none min-w-52 pl-4"
class="h-full {orientation === 'vertical'
? 'min-w-52'
: 'w-full pr-4'} border-none shadow-none pl-4"
>
<Card.Content
class="h-full flex {$elevationProfile
class="h-full flex {orientation === 'vertical'
? 'flex-col justify-center'
: 'flex-row w-full justify-between'} flex-wrap gap-4 p-0"
: 'flex-row w-full justify-between'} gap-4 p-0"
>
<Tooltip>
<span slot="data" class="flex flex-row items-center">
@@ -48,8 +50,8 @@
</span>
<span slot="tooltip">{$_('quantities.elevation')}</span>
</Tooltip>
{#if $bottomPanelSize > 120 || !$elevationProfile}
<Tooltip>
{#if panelSize > 120 || orientation === 'horizontal'}
<Tooltip class={orientation === 'horizontal' ? 'hidden xs:block' : ''}>
<span slot="data" class="flex flex-row items-center">
<Zap size="18" class="mr-1" />
<WithUnits value={statistics.global.speed.moving} type="speed" showUnits={false} />
@@ -57,14 +59,14 @@
<WithUnits value={statistics.global.speed.total} type="speed" />
</span>
<span slot="tooltip"
>{$velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_(
>{velocityUnits === 'speed' ? $_('quantities.speed') : $_('quantities.pace')} ({$_(
'quantities.moving'
)} / {$_('quantities.total')})</span
>
</Tooltip>
{/if}
{#if $bottomPanelSize > 160 || !$elevationProfile}
<Tooltip>
{#if panelSize > 160 || orientation === 'horizontal'}
<Tooltip class={orientation === 'horizontal' ? 'hidden md:block' : ''}>
<span slot="data" class="flex flex-row items-center">
<Timer size="18" class="mr-1" />
<WithUnits value={statistics.global.time.moving} type="time" />

View File

@@ -5,7 +5,7 @@
</script>
<Tooltip.Root>
<Tooltip.Trigger>
<Tooltip.Trigger {...$$restProps}>
<slot name="data" />
</Tooltip.Trigger>
<Tooltip.Content {side}>

View File

@@ -15,7 +15,7 @@
</script>
{#if module !== undefined}
<div class="markdown">
<div class="markdown space-y-3">
<svelte:component this={module} />
</div>
{/if}
@@ -26,13 +26,13 @@
:global(.markdown h1) {
@apply text-3xl;
@apply font-bold;
@apply mb-3;
@apply font-semibold;
@apply mb-6;
}
:global(.markdown h2) {
@apply text-2xl;
@apply font-bold;
@apply font-semibold;
@apply mb-3;
}
@@ -41,6 +41,11 @@
@apply hover:underline;
}
:global(.markdown p > a) {
@apply text-blue-500;
@apply hover:underline;
}
:global(.markdown ul) {
@apply list-disc;
@apply pl-4;

View File

@@ -16,44 +16,42 @@
import ToolbarItemMenu from './ToolbarItemMenu.svelte';
</script>
<div class="absolute top-0 bottom-0 left-0 z-20 flex flex-col justify-center pointer-events-none">
<div class="flex flex-row w-screen items-center">
<div
class="h-fit flex flex-col p-1 gap-1.5 bg-background rounded-r-md pointer-events-auto shadow-md"
>
<ToolbarItem tool={Tool.ROUTING}>
<Pencil slot="icon" size="18" class="h-" />
<span slot="tooltip">{$_('toolbar.routing.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.WAYPOINT}>
<MapPin slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.waypoint.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.SCISSORS}>
<Scissors slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.scissors.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.TIME}>
<CalendarClock slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.time.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.MERGE}>
<Group slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.merge.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.EXTRACT}>
<Ungroup slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.extract.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.REDUCE}>
<Filter slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.reduce.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.CLEAN}>
<SquareDashedMousePointer slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.clean.tooltip')}</span>
</ToolbarItem>
</div>
<ToolbarItemMenu />
<div class="flex flex-row w-full items-center">
<div
class="h-fit flex flex-col p-1 gap-1.5 bg-background rounded-r-md pointer-events-auto shadow-md"
>
<ToolbarItem tool={Tool.ROUTING}>
<Pencil slot="icon" size="18" class="h-" />
<span slot="tooltip">{$_('toolbar.routing.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.WAYPOINT}>
<MapPin slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.waypoint.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.SCISSORS}>
<Scissors slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.scissors.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.TIME}>
<CalendarClock slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.time.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.MERGE}>
<Group slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.merge.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.EXTRACT}>
<Ungroup slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.extract.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.REDUCE}>
<Filter slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.reduce.tooltip')}</span>
</ToolbarItem>
<ToolbarItem tool={Tool.CLEAN}>
<SquareDashedMousePointer slot="icon" size="18" />
<span slot="tooltip">{$_('toolbar.clean.tooltip')}</span>
</ToolbarItem>
</div>
<ToolbarItemMenu />
</div>

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { Tool, currentTool } from '$lib/stores';
import { settings } from '$lib/db';
import { flyAndScale } from '$lib/utils';
import * as Card from '$lib/components/ui/card';
import Routing from '$lib/components/toolbar/tools/routing/Routing.svelte';
@@ -14,6 +15,8 @@
import { onMount } from 'svelte';
import mapboxgl from 'mapbox-gl';
const { minimizeRoutingMenu } = settings;
let popupElement: HTMLElement;
let popup: mapboxgl.Popup;
@@ -36,7 +39,7 @@
<Card.Root class="rounded-md border-none">
<Card.Content class="p-2.5">
{#if $currentTool === Tool.ROUTING}
<Routing {popup} {popupElement} />
<Routing {popup} {popupElement} bind:minimized={$minimizeRoutingMenu} />
{:else if $currentTool === Tool.SCISSORS}
<Scissors />
{:else if $currentTool === Tool.WAYPOINT}

View File

@@ -36,13 +36,15 @@
import { onDestroy, onMount } from 'svelte';
import { TrackPoint } from 'gpx';
export let popup: mapboxgl.Popup;
export let popupElement: HTMLElement;
export let minimized = false;
export let minimizable = true;
export let popup: mapboxgl.Popup | undefined = undefined;
export let popupElement: HTMLElement | undefined = undefined;
let selectedItem: ListItem | null = null;
const { minimizeRoutingMenu, privateRoads, routing } = settings;
const { privateRoads, routing } = settings;
$: if ($map) {
$: if ($map && popup && popupElement) {
// remove controls for deleted files
routingControls.forEach((controls, fileId) => {
if (!$fileObservers.has(fileId)) {
@@ -93,9 +95,9 @@
});
</script>
{#if $minimizeRoutingMenu}
{#if minimized}
<div class="-m-1.5 -mb-2">
<Button variant="ghost" class="px-1 h-[26px]" on:click={() => ($minimizeRoutingMenu = false)}>
<Button variant="ghost" class="px-1 h-[26px]" on:click={() => (minimized = false)}>
<SquareArrowOutDownRight size="18" />
</Button>
</div>
@@ -207,9 +209,11 @@
<div>{$_('toolbar.routing.help')}</div>
{/if}
</Help>
<Button variant="ghost" class="px-1 h-6" on:click={() => ($minimizeRoutingMenu = true)}>
<SquareArrowUpLeft size="18" />
</Button>
{#if minimizable}
<Button variant="ghost" class="px-1 h-6" on:click={() => (minimized = true)}>
<SquareArrowUpLeft size="18" />
</Button>
{/if}
</div>
</div>
{/if}

View File

@@ -82,9 +82,11 @@ function dexieUninitializedSettingStore(setting: string, initial: any): Writable
export const settings = {
distanceUnits: dexieSettingStore<'metric' | 'imperial'>('distanceUnits', 'metric'),
velocityUnits: dexieSettingStore('velocityUnits', 'speed'),
temperatureUnits: dexieSettingStore('temperatureUnits', 'celsius'),
velocityUnits: dexieSettingStore<'speed' | 'pace'>('velocityUnits', 'speed'),
temperatureUnits: dexieSettingStore<'celsius' | 'fahrenheit'>('temperatureUnits', 'celsius'),
elevationProfile: dexieSettingStore('elevationProfile', true),
additionalDatasets: dexieSettingStore<string[]>('additionalDatasets', []),
elevationFill: dexieSettingStore<'slope' | 'surface' | undefined>('elevationFill', undefined),
verticalFileView: dexieSettingStore<boolean>('fileView', false),
mode: dexieSettingStore('mode', (() => {
let currentMode: string | undefined = get(mode);

View File

@@ -1,7 +1,7 @@
# Help keep the website free (and ad-free) 🙏
Each time you add or move a track point, we make a request to our servers to retrieve a route on the road network.
We also rely on APIs from [Mapbox](https://mapbox.com) to load beautiful maps, retrieve elevation data and process geocoding requests (looking for a place in the search bar).
Each time you add or move GPS points, we make a request to our servers to retrieve a route on the road network.
We also rely on APIs from <a href="https://mapbox.com" target="_blank">Mapbox</a> to load beautiful maps, retrieve elevation data and process geocoding requests (looking for a place in the search bar).
Unfortunately this is very costly so if you like the tool and use it frequently, please consider making even a small donation so that this website can stay **free** and **ad-free**.
Thank you very much for your support! ❤️

View File

@@ -1,4 +1,5 @@
[Mapbox](https://mapbox.com) is the company that provides some of the beautiful maps on this website.
They also develop the [map engine](https://github.com/mapbox/mapbox-gl-js) which powers *gpx.studio*.
We are incredibly lucky and grateful to have joined their [Community](https://www.mapbox.com/community) program, which supports nonprofits, educational institutions, and positive impact organizations.
Mapbox is the company that provides some of the beautiful maps on this website.
They also develop the <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">map engine</a> which powers *gpx.studio*.
We are incredibly lucky and grateful to have joined their <a href="https://mapbox.com/community" target="_blank">Community</a> program, which supports nonprofits, educational institutions, and positive impact organizations.
This means that *gpx.studio* can benefit from Mapbox tools at discounted prices, which greatly contributes to the financial viability of the project and to offering the best possible user experience.

View File

@@ -0,0 +1,3 @@
# Global and local maps
A large collection of basemaps and overlays to help you craft your next outdoor adventure, or visualize your latest achievement.

View File

@@ -0,0 +1,3 @@
# Data visualization
An interactive elevation profile with detailed statistics to analyze recorded activities and future objectives.

View File

@@ -0,0 +1,3 @@
# Route planning
An intuitive interface to create itineraries tailored to each sport, based on <a href="https://www.openstreetmap.org" target="_blank">OpenStreetMap</a> data.

View File

@@ -0,0 +1,3 @@
# Advanced file processing tools
A suite of tools for performing all common file processing tasks, and which can be applied to multiple files at once.

View File

@@ -1,6 +1,7 @@
# Translation 🗣
The website is translated by volunteers on a collaborative translation platform.
You can help complete and improve the translations by joining the [Crowdin project](https://crowdin.com/project/gpxstudio).
You can help complete and improve the translations by joining the <a href="https://crowdin.com/project/gpxstudio" target="_blank">Crowdin project</a>.
[Get in touch](#contact) if you would like to start the translation in a new language.
Any help is greatly appreciated!

View File

@@ -2,7 +2,7 @@
"metadata": {
"app_title": "the online GPX file editor",
"about_title": "about",
"description": "View, edit and create GPX files online with advanced route planning capabilities, file processing tools and beautiful maps."
"description": "View, edit and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations."
},
"menu": {
"new": "New",

View File

@@ -3,10 +3,34 @@
import { Button } from '$lib/components/ui/button';
import DocsLoader from '$lib/components/docs/DocsLoader.svelte';
import Logo from '$lib/components/Logo.svelte';
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
import Routing from '$lib/components/toolbar/tools/routing/Routing.svelte';
import { languages } from '$lib/languages';
import { Heart } from 'lucide-svelte';
import { mode } from 'mode-watcher';
import { settings } from '$lib/db';
import { BookOpenText, Heart, Map } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import { exampleGPXFile } from '$lib/assets/example';
import { writable } from 'svelte/store';
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
import { currentTool, Tool } from '$lib/stores';
import { onDestroy, onMount } from 'svelte';
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable<'slope' | 'surface' | undefined>(undefined);
const { distanceUnits, velocityUnits, temperatureUnits } = settings;
onMount(() => {
currentTool.set(Tool.SCISSORS);
});
onDestroy(() => {
currentTool.set(null);
});
</script>
<svelte:head>
@@ -27,55 +51,145 @@
{/each}
</svelte:head>
<div>
<div class="p-12">TODO hero section</div>
<div class="p-12">
<div>TODO show toolbar, advanced route planning and file editing tools</div>
<div class="flex flex-col gap-y-24 my-24">
<div class="px-12 w-full flex flex-col items-center">
<div class="flex flex-col gap-6 items-center max-w-3xl">
<div class="text-6xl font-black text-center">{$_('metadata.app_title')}</div>
<div class="text-xl text-muted-foreground text-center">
{$_('metadata.description')}
</div>
<div class="w-full flex flex-row justify-center gap-3">
<Button href="./" class="w-1/3 min-w-fit">
<Map size="18" class="mr-1.5" />
{$_('homepage.app')}
</Button>
<Button variant="secondary" href="./documentation" class="w-1/3 min-w-fit">
<BookOpenText size="18" class="mr-1.5" />
<span>{$_('homepage.documentation')}</span>
</Button>
</div>
</div>
</div>
<div class="px-24 w-full flex flex-col items-center">
<div class="flex flex-col md:flex-row gap-x-12 gap-y-6 items-center justify-between max-w-5xl">
<div class="text-center [&>*>p]:text-muted-foreground">
<DocsLoader path="about/routing.md" />
</div>
<div class="p-3 w-fit rounded-md border shadow-xl">
<Routing minimizable={false} />
</div>
</div>
</div>
<div class="px-24 w-full flex flex-col items-center">
<div class="flex flex-col md:flex-row gap-x-12 gap-y-6 items-center justify-between max-w-5xl">
<div class="text-center md:hidden [&>*>p]:text-muted-foreground">
<DocsLoader path="about/tools.md" />
</div>
<div class="relative">
<Toolbar />
</div>
<div class="text-center hidden md:block [&>*>p]:text-muted-foreground">
<DocsLoader path="about/tools.md" />
</div>
</div>
</div>
<div class="px-24 w-full flex flex-col items-center">
<div
class="markdown flex flex-col md:flex-row gap-x-12 gap-y-6 items-center justify-between max-w-5xl"
>
<div class="text-center [&>*>p]:text-muted-foreground">
<DocsLoader path="about/maps.md" />
</div>
<div
class="relative grow min-w-1/2 min-h-96 aspect-square rounded-2xl shadow-xl overflow-clip"
>
<img
src="{base}/mapbox-outdoors.png"
alt="Mapbox Outdoors map screenshot."
class="absolute"
style="clip-path: inset(0 50% 50% 0);"
/>
<img
src="{base}/mapbox-satellite.png"
alt="Mapbox Satellite map screenshot."
class="absolute"
style="clip-path: inset(0 0 50% 50%);"
/>
<img
src="{base}/ign.png"
alt="IGN map screenshot."
class="absolute"
style="clip-path: inset(50% 50% 0 0);"
/>
<img
src="{base}/cyclosm.png"
alt="CyclOSM map screenshot."
class="absolute"
style="clip-path: inset(50% 0 0 50%);"
/>
<img src="{base}/waymarked.png" alt="Waymarked Trails map screenshot." class="absolute" />
</div>
</div>
</div>
<div class="p-6 md:p-12">
<div class="text-center mb-6 [&>*>p]:text-muted-foreground">
<DocsLoader path="about/plot.md" />
</div>
<div class="flex flex-col items-center">
<div class="h-48 w-full">
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
additionalDatasets={$additionalDatasets}
elevationFill={$elevationFill}
panelSize={200}
distanceUnits={$distanceUnits}
velocityUnits={$velocityUnits}
temperatureUnits={$temperatureUnits}
/>
</div>
<div class="h-10 w-fit">
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={192}
orientation={'horizontal'}
velocityUnits={$velocityUnits}
/>
</div>
</div>
</div>
<div class="relative">
<img src="{base}/map.png" alt="Screenshot of the gpx.studio app." class="w-full" />
<img src="{base}/map.png" alt="Screenshot of the gpx.studio map in 3D." class="w-full" />
<div
class="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-background via-transparent to-background"
/>
</div>
<div class="p-12">
<div>TODO elevation profile (use component?)</div>
<img
src="{base}/profile-{$mode ?? 'light'}.png"
alt="Screenshot of an elevation profile in the gpx.studio app."
class="w-full"
/>
</div>
<div class="p-12">
<div>TODO show several squares with different basemaps of the same place</div>
</div>
<div class="p-12 flex flex-col gap-12">
<div class="flex flex-col items-center gap-6">
<DocsLoader path="about/funding.md" />
<Button
href="https://ko-fi.com/gpxstudio"
target="_blank"
class="w-1/3 min-w-fit bg-support text-base"
>
<Heart size="16" class="ml-1 mr-1" fill="rgb(var(--support))" />
<span>{$_('homepage.support_button')}</span>
</Button>
</div>
<div class="flex flex-col items-center gap-6">
<DocsLoader path="about/translation.md" />
</div>
<div
class="flex flex-col md:flex-row items-center justify-center gap-x-12 gap-y-6 p-6 mx-6 border rounded-md shadow-lg"
<div class="px-12 flex flex-col items-center gap-6">
<DocsLoader path="about/funding.md" />
<Button
href="https://ko-fi.com/gpxstudio"
target="_blank"
class="w-1/3 min-w-fit bg-support text-base"
>
<div class="shrink-0 flex flex-col sm:flex-row md:flex-col items-center gap-x-4 gap-y-2">
<div class="text-lg font-semibold text-muted-foreground">
❤️ {$_('homepage.supported_by')}
</div>
<a href="https://www.mapbox.com/" target="_blank">
<Logo company="mapbox" class="w-60" />
</a>
<Heart size="16" class="mr-1" fill="rgb(var(--support))" />
<span>{$_('homepage.support_button')}</span>
</Button>
</div>
<div class="px-12">
<DocsLoader path="about/translation.md" />
</div>
<div
class="mx-24 flex flex-col md:flex-row items-center justify-center gap-x-12 gap-y-6 p-6 border rounded-md shadow-xl"
>
<div class="shrink-0 flex flex-col sm:flex-row md:flex-col items-center gap-x-4 gap-y-2">
<div class="text-lg font-semibold text-muted-foreground">
❤️ {$_('homepage.supported_by')}
</div>
<DocsLoader path="about/mapbox.md" />
<a href="https://www.mapbox.com/" target="_blank">
<Logo company="mapbox" class="w-60" />
</a>
</div>
<DocsLoader path="about/mapbox.md" />
</div>
</div>

BIN
website/static/cyclosm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

BIN
website/static/ign.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

View File

@@ -6,13 +6,6 @@ const config = {
content: ["./src/**/*.{html,js,svelte,ts}"],
safelist: ["dark"],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px"
}
},
extend: {
colors: {
border: "hsl(var(--border) / <alpha-value>)",
@@ -57,6 +50,9 @@ const config = {
},
fontFamily: {
sans: [...fontFamily.sans]
},
screens: {
"xs": "500px",
}
}
},