Compare commits

..

3 commits

Author SHA1 Message Date
2779333758 pre render true
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-07-19 18:08:23 +02:00
6aea39b08c Update build info
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2023-07-19 17:29:36 +02:00
631c9b75f9 add register false 2023-07-19 17:29:16 +02:00
73 changed files with 4696 additions and 10373 deletions

View file

@ -1,4 +1,2 @@
TAG=0.5.0 TAG=0.5.0
ORIGIN=https://pianello.webdeploy.it ORIGIN=https://pianello.webdeploy.it
PUBLIC_BACKEND_URL=https://ale-dev.teck-developer.com
BODY_SIZE_LIMIT=Infinity

2
.gitignore vendored
View file

@ -2,4 +2,4 @@ node_modules/
dist/ dist/
service-worker.build.js service-worker.build.js
.env .env
static/images/mockups static/images/mockups

View file

@ -1,23 +1,26 @@
steps: pipeline:
build_and_deploy: build_and_deploy:
image: git.webdeploy.it/webdeploy/alpine image: alpine:3.14
secrets: [docker_password, docker_username, runner_private_key] secrets: [docker_password, docker_username, runner_private_key]
commands: commands:
- apk add --no-cache openssh docker docker-compose jq
################### Provisioning ################### Provisioning
- echo "$${DOCKER_PASSWORD}" | docker login --password-stdin --username "$${DOCKER_USERNAME}" git.webdeploy.it - echo "$${DOCKER_PASSWORD}" | docker login --password-stdin --username "$${DOCKER_USERNAME}" git.webdeploy.it
- echo "TAG=$(jq -r .version ./frontend/package.json)" >> .env - echo "TAG=$(jq -r .version ./frontend/package.json)" >> .env
- echo "ORIGIN=https://pianello.webdeploy.it" >> .env - echo "ORIGIN=https://pianello.webdeploy.it" >> .env
- echo "PUBLIC_BACKEND_URL=https://ale-dev.teck-developer.com" >> ./frontend/.env - docker-compose -f docker-compose.build.yml build
- docker-compose -f docker-compose.build.yml build
- docker push git.webdeploy.it/pianello/frontend:latest - docker push git.webdeploy.it/pianello/frontend:latest
- docker push git.webdeploy.it/pianello/frontend:$(jq -r .version ./frontend/package.json) - docker push git.webdeploy.it/pianello/frontend:$(jq -r .version ./frontend/package.json)
#### Deploy # DEPLOY STUFF
- config_ssh.sh "$${RUNNER_PRIVATE_KEY}" - eval $(ssh-agent -s)
- mkdir -p ~/.ssh
- printf "%s" "$${RUNNER_PRIVATE_KEY}" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh-add ~/.ssh/id_ed25519
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
- cat .env | ssh pianello@pianello.webdeploy.it "cat - > .env" # copy env - cat .env | ssh pianello@pianello.webdeploy.it "cat - > .env" # copy env
- cat docker-compose.prod.yml | ssh pianello@pianello.webdeploy.it "cat - > docker-compose.yml && docker compose pull && docker compose up -d --remove-orphans" - cat docker-compose.prod.yml | ssh pianello@pianello.webdeploy.it "cat - > docker-compose.yml && docker compose pull && docker compose stop && docker compose up -d --remove-orphans"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
when: branches: maybeitworked
branch: main

View file

@ -3,8 +3,6 @@
### CI/CD ### CI/CD
[![status-badge](https://cicd.webdeploy.it/api/badges/pianello/pianello-web-app/status.svg)](https://cicd.webdeploy.it/pianello/pianello-web-app) [![status-badge](https://cicd.webdeploy.it/api/badges/pianello/pianello-web-app/status.svg)](https://cicd.webdeploy.it/pianello/pianello-web-app)
[![status-badge](https://kuma.dashboard.webdeploy.it/api/badge/28/uptime/400)](https://kuma.dashboard.webdeploy.it)
### Legal ### Legal
This project is licensed under the [GNU General Public License v3](./LICENSE.md). This project is licensed under the [GNU General Public License v3](./LICENSE.md).

View file

@ -7,8 +7,6 @@ services:
build: build:
context: frontend context: frontend
dockerfile: Dockerfile dockerfile: Dockerfile
args:
PUBLIC_BACKEND_URL: ${PUBLIC_BACKEND_URL}
version_tag: version_tag:
extends: frontend extends: frontend

View file

@ -7,6 +7,6 @@ services:
environment: environment:
ORIGIN: "${ORIGIN}" ORIGIN: "${ORIGIN}"
PORT: 8000 PORT: 8000
BODY_SIZE_LIMIT: Infinity BODY_SIZE_LIMIT: 0
ports: ports:
- 127.0.0.1:8000:8000 - 127.0.0.1:8000:8000

1
frontend/.gitignore vendored
View file

@ -8,4 +8,3 @@ node_modules
!.env.example !.env.example
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
.pnp*

2
frontend/.npmrc Normal file
View file

@ -0,0 +1,2 @@
engine-strict=true
resolution-mode=highest

View file

@ -1,20 +1,19 @@
FROM git.webdeploy.it/webdeploy/yarn AS build FROM node:18 AS build
WORKDIR /usr WORKDIR /usr
COPY package.json ./ COPY package.json ./
COPY package-lock.json ./ COPY yarn.lock ./
RUN npm ci RUN yarn install
COPY . ./ COPY . ./
# RUN npm run check RUN yarn build
RUN npm run build
FROM git.webdeploy.it/webdeploy/yarn FROM node:18-alpine3.16
WORKDIR /app WORKDIR /app
COPY package.json ./ COPY package.json ./
COPY package-lock.json ./ COPY yarn.lock ./
RUN npm ci RUN yarn install --frozen-lockfile
COPY --from=build /usr/build /app COPY --from=build /usr/build /app
EXPOSE 8000 EXPOSE 8000
CMD ["node", "/app"] CMD ["node", "/app"]

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
{ {
"name": "frontend", "name": "frontend",
"version": "0.6.0", "version": "0.5.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build && ./add-sigint.sh", "build": "vite build && yarn add-sigint",
"add-sigint": "./add-sigint.sh", "add-sigint": "./add-sigint.sh",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@ -13,28 +13,27 @@
"format": "prettier --plugin-search-dir . --write ." "format": "prettier --plugin-search-dir . --write ."
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^4.0.1", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^2.5.2", "@sveltejs/kit": "^1.22.3",
"@sveltejs/vite-plugin-svelte": "^3.0.2", "@typescript-eslint/eslint-plugin": "^6.1.0",
"@types/leaflet": "^1.9.8", "@typescript-eslint/parser": "^6.1.0",
"@typescript-eslint/eslint-plugin": "^7.1.1", "@vite-pwa/sveltekit": "^0.2.5",
"@typescript-eslint/parser": "^7.1.1", "eslint": "^8.45.0",
"eslint": "^8.57.0", "eslint-config-prettier": "^8.5.0",
"eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.30.0",
"eslint-plugin-svelte": "^2.35.1", "prettier": "^3.0.0",
"prettier": "^3.2.5", "prettier-plugin-svelte": "^3.0.0",
"prettier-plugin-svelte": "^3.2.2", "svelte": "^4.1.0",
"svelte": "^4.2.12", "svelte-check": "^3.4.6",
"svelte-check": "^3.6.6", "tslib": "^2.4.1",
"tslib": "^2.6.2", "typescript": "^5.0.0",
"typescript": "^5.3.3", "vite": "^4.4.4",
"vite": "^5.1.5" "vite-plugin-pwa": "^0.16.4"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@sentry/sveltekit": "^7.105.0", "@sveltejs/adapter-node": "^1.3.1",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"leaflet-gpx": "^1.7.0", "svelte-preprocess": "^5.0.4"
"svelte-preprocess": "^5.1.3"
} }
} }

View file

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

73
frontend/src/app.d.ts vendored
View file

@ -2,75 +2,20 @@
// for information about these interfaces // for information about these interfaces
declare global { declare global {
namespace App { namespace App {
export interface Error { // interface Error {}
message: string;
}
// interface Locals {} // interface Locals {}
// interface PageData {} // interface PageData {}
// interface Platform {} // interface Platform {}
interface Category {
id: 1;
name_it: 'Natura';
name_en: 'Nature';
description_it: 'Giri nella natura';
description_en: 'Countryside routes';
icon: '';
created_at: null;
updated_at: null;
deleted_at: null;
color: string;
cover: string;
}
interface Sport {
id: 1;
name_it: 'Trekking';
name_en: 'Trekking';
description_it: 'Percorso escursionistico';
description_en: 'Trekking route';
icon: '';
created_at: '2023-10-31T18:37:30.000000Z';
updated_at: null;
deleted_at: null;
}
interface SportDetails {
id: 1;
route_id: 1;
sport_id: 1;
short_description_it: 'Percorso escursionistico intermedio. Buon allenamento richiesto. Sentieri facilmente percorribili. Adatto a ogni livello di abilit\u00e0. ';
short_description_en: 'Intermediate hiking route. Good training required. Easily accessible paths. Suitable for all skill levels.';
gpx_path: '';
distance: 16800;
duration: 288;
elevation_gain: 439;
elevation_loss: null;
altitude_max: 620;
altitude_min: 180;
difficulty_it: 'Facile';
difficulty_en: 'Easy';
route_type_it: 'Percorso ad anello';
route_type_en: 'Ring route';
created_at: '2023-11-02T10:57:41.000000Z';
updated_at: null;
deleted_at: null;
sport: Sport;
}
interface Route { interface Route {
id: number; id: number;
cover: string; name: string;
name_it: string; duration: number;
name_en: string; image: string;
/* Sometimes from API comes 'title' instead of 'name' :c */
title_it: string;
title_en: string;
description_it: string;
description_en: string;
route_category_id: number;
created_at: '2023-11-02T10:50:07.000000Z';
updated_at: null;
deleted_at: null;
route_sport_details: Array<SportDetails>;
elevation_gain?: number;
} }
} }
} }
export { Route, Category, Sport, SportDetails };
export {
Route
};

View file

@ -1,48 +1,48 @@
<!doctype html> <!DOCTYPE html>
<html lang="it"> <html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
<link rel="manifest" href="manifest.json" />
%sveltekit.head% <head>
</head> <meta charset="utf-8" />
<style> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
:root { <meta name="viewport" content="width=device-width" />
--footer-height: 108px; %sveltekit.head%
--accent-color: #213c8b; </head>
--pianello-red: #de0e1b; <style>
--pianello-yellow: #f6ae04; :root {
--pianello-blue: #213c8b; --footer-height: calc(68px + 20px + 20px);
--card-background-color: #f9f4f1; --accent-color: #213c8b;
} --pianello-red: #de0e1b;
--pianello-yellow: #f6ae04;
--pianello-blue: #213c8b;
--card-background-color: #f9f4f1;
}
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
html { html {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
body { body {
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0px; margin: 0px;
background-color: #fff; background-color: #fff;
font-family: 'Roboto-Regular'; font-family: 'Roboto-Regular';
display: grid; display: grid;
} }
@font-face { @font-face {
font-family: 'Roboto-Regular'; font-family: 'Roboto-Regular';
src: url('/fonts/roboto/Roboto-Regular.ttf') format('TrueType'); src: url('/fonts/roboto/Roboto-Regular.ttf') format('TrueType');
} }
</style> </style>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html> </html>

View file

@ -1,41 +1,42 @@
:root { :root {
--footer-height: calc(68px + 20px + 20px); --footer-height: calc(68px + 20px + 20px);
--accent-color: #213c8b; --accent-color: #213c8b;
--pianello-red: #de0e1b; --pianello-red: #de0e1b;
--pianello-yellow: #f6ae04; --pianello-yellow: #f6ae04;
--pianello-blue: #213c8b; --pianello-blue: #213c8b;
--card-background-color: #f9f4f1; --card-background-color: #f9f4f1;
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
html { html {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
body { body {
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0px; margin: 0px;
background-color: #fff; background-color: #fff;
font-family: 'Roboto-Regular'; font-family: 'Roboto-Regular';
display: grid; display: grid;
} }
header, header,
main, main,
footer { footer {
background: white; background: white;
height: 100%; height: 100%;
width: 100%; width: 100%;
display: grid; display: grid;
place-items: center; place-items: center;
} }
@font-face { @font-face {
font-family: 'Roboto-Regular'; font-family: 'Roboto-Regular';
src: url('/fonts/roboto/Roboto-Regular.ttf') format('TrueType'); src: url('/fonts/roboto/Roboto-Regular.ttf') format('TrueType');
} }

View file

@ -1,13 +0,0 @@
.header {
padding: 15px;
background-color: transparent;
color: black;
margin: 0 auto;
padding-left: 25px;
padding-right: 25px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
height: 70px;
}

View file

@ -1,41 +0,0 @@
<html>
<head>
<style>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
background-image: url(/splash.webp);
background-size: cover;
display: grid;
place-content: center;
height: 100%;
font-size: 24px;
font-family: monospace;
}
.wrapper {
display: flex;
backdrop-filter: blur(5px);
border-radius: 10px;
border: 1px solid black;
}
#status {
border-right: 1px solid black;
}
p {
padding: 20px;
}
</style>
</head>
<body>
<div class="wrapper">
<p id="status">Code %sveltekit.status%</p>
<p id="message">%sveltekit.error.message%</p>
</div>
</body>
</html>

View file

@ -1,7 +0,0 @@
export async function handleError({ error, message }) {
console.error(error);
return {
message
};
}

View file

@ -1,16 +0,0 @@
export async function handle({ event, resolve }) {
const response = await resolve(event);
return response;
}
export async function handleFetch({ request, fetch }) {
return fetch(request);
}
export async function handleError({ error, message }) {
console.error(error);
return {
message
};
}

View file

@ -0,0 +1,75 @@
<script lang="ts">
import { useRegisterSW } from 'virtual:pwa-register/svelte'
const {
needRefresh,
updateServiceWorker,
offlineReady
} = useRegisterSW({
onRegistered(r) {
// uncomment following code if you want check for updates
// r && setInterval(() => {
// console.log('Checking for sw update')
// r.update()
// }, 20000 /* 20s for testing purposes */)
console.log(`SW Registered: ${r}`)
},
onRegisterError(error) {
console.log('SW registration error', error)
},
})
const close = () => {
offlineReady.set(false)
needRefresh.set(false)
}
$: toast = $offlineReady || $needRefresh
</script>
{#if toast}
<div class="pwa-toast" role="alert">
<div class="message">
{#if $offlineReady}
<span>
App ready to work offline
</span>
{:else}
<span>
New content available, click on reload button to update.
</span>
{/if}
</div>
{#if $needRefresh}
<button on:click={() => updateServiceWorker(true)}>
Reload
</button>
{/if}
<button on:click={close}>
Close
</button>
</div>
{/if}
<style>
.pwa-toast {
position: fixed;
right: 0;
bottom: 0;
margin: 16px;
padding: 12px;
border: 1px solid #8885;
border-radius: 4px;
z-index: 2;
text-align: left;
box-shadow: 3px 4px 5px 0 #8885;
background-color: white;
}
.pwa-toast .message {
margin-bottom: 8px;
}
.pwa-toast button {
border: 1px solid #8885;
outline: none;
margin-right: 5px;
border-radius: 2px;
padding: 3px 10px;
}
</style>

View file

@ -16,10 +16,8 @@
#container-of-container { #container-of-container {
width: 100%; width: 100%;
position: fixed; position: fixed;
bottom: 10px; bottom: 0;
left: 0; left: 0;
display: grid;
height: 10dvh;
} }
#container { #container {
display: flex; display: flex;
@ -30,6 +28,7 @@
box-shadow: 0 0 50px #ccc; box-shadow: 0 0 50px #ccc;
background-color: var(--card-background-color); background-color: var(--card-background-color);
margin: 0 auto; margin: 0 auto;
margin-bottom: 10px;
} }
#container > a { #container > a {

View file

@ -1,40 +1,47 @@
<script lang="ts"> <script lang="ts">
import '../../css/header.css'; export const title: string = 'Naturalistici';
export const title: string = 'Naturalistici'; const goBack = () => {
history.back();
const goBack = () => { };
history.back();
};
</script> </script>
<div class="header"> <div>
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-missing-attribute --> <!-- svelte-ignore a11y-missing-attribute -->
<a on:click={goBack}> <a on:click={goBack}>
<img class="back" src="/images/black-back-arrow.png" alt="" /> <img class="back" src="/images/black-back-arrow.png" alt="" />
</a> </a>
<img src="/images/app-bar-logo.png" alt="" /> <img src="/images/app-bar-logo.png" alt="" />
</div> </div>
<style> <style>
div { div {
background-color: transparent; position: fixed;
color: black; padding: 15px;
} background-color: transparent;
div a { color: black;
cursor: pointer; margin: 0 auto;
} padding-left: 15px;
div a img { padding-right: 15px;
height: 40px; width: 100%;
} display: flex;
justify-content: space-between;
align-items: center;
}
div a {
cursor: pointer;
}
div a img {
height: 40px;
}
img { img {
height: 40px; height: 40px;
} }
.back { .back {
height: 20px; height: 20px;
} }
</style> </style>

View file

@ -1,33 +1,41 @@
<script lang="ts"> <script lang="ts">
import '../../css/header.css'; export const title: string = 'Naturalistici';
export let title: string = 'Naturalistici';
const goBack = () => { const goBack = () => {
history.back(); history.back();
}; };
</script> </script>
<div class="header"> <div>
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-missing-attribute --> <!-- svelte-ignore a11y-missing-attribute -->
<a on:click={goBack}> <a on:click={goBack}>
<img class="back" src="/images/white-back-arrow.png" alt="" /> <img class="back" src="/images/white-back-arrow.png" alt="" />
</a> </a>
<p>{title}</p> <p>Percorsi <b>{title}</b></p>
<img src="/images/app-bar-logo.png" alt="" /> <img src="/images/app-bar-logo.png" alt="" />
</div> </div>
<style> <style>
div { div {
color: white; color: white;
margin: 0 auto;
padding: 15px;
padding-left: 25px;
padding-right: 25px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #de0e1b; background-color: #de0e1b;
position: fixed;
font-size: 22px;
} }
div p { div p {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-weight: bold;
} }
div a { div a {
cursor: pointer; cursor: pointer;

View file

@ -1,77 +1,85 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_BACKEND_URL } from '$env/static/public'; export let type: string;
export let name: string;
export let id: number;
export let color: string; export let color: string;
export const path = `/paths/${id}`; export const path = `/paths/${type}`;
export let src: string;
let image = '/archi.png';
switch (id) {
case 1:
image = '/archi.png';
break;
case 2:
image = '/montagne.png';
break;
case 3:
image = '/bibbito.png';
break;
}
</script> </script>
<a href={path} class="route-card"> <a data-sveltekit-reload href={path} class="route-card">
<div class="route-card-left" style="background-color: {color}"> <div class="route-card-left" style="background-color: {color}">
<img src={image} alt="logo" /> <img src="/images/app-bar-logo.png" alt="logo" />
</div> </div>
<div class="route-card-center"> <div class="route-card-center">
<div class="name"> <div class="name">
<div class="bold">{name}</div> <div>Percorsi</div>
<div class="bold">{type}</div>
</div> </div>
</div> </div>
<div class="route-card-right" style:background-image="url({PUBLIC_BACKEND_URL}{src})" /> <div class="route-card-right" />
</a> </a>
<style> <style>
a { a {
text-decoration: inherit; text-decoration: inherit;
color: inherit; color: inherit;
cursor: pointer; cursor: auto;
display: grid;
grid-template-columns: 20% minmax(1px, 1fr) 20%;
max-width: 100%;
width: calc(100% - 20px);
margin: 0 auto;
height: 100%;
font-size: 24px;
} }
.bold {
font-weight: bold;
}
.route-card {
text-decoration: none;
display: block;
flex: 1;
--route-card-radius: 45px;
border-radius: var(--route-card-radius);
box-shadow: 0 0 27px #ccc;
margin-top: 10px;
margin-bottom: 10px;
width: 90%;
display: flex;
background-color: var(--card-background-color);
}
.route-card-center {
font-size: 18px;
}
.route-card > * {
flex: 1;
}
.route-card-left {
flex: 0 1 20%;
border-top-left-radius: var(--route-card-radius);
border-bottom-left-radius: var(--route-card-radius);
display: grid;
place-items: center;
}
.route-card-left img {
display: block;
width: 80%;
height: auto;
}
.route-card-right { .route-card-right {
border-top-right-radius: 45px; flex: 0 1 20%;
border-bottom-right-radius: 45px; border-top-right-radius: var(--route-card-radius);
border-bottom-right-radius: var(--route-card-radius);
background-image: url('/images/test-1.jpg');
background-position: center; background-position: center;
background-size: cover; background-size: cover;
} }
.route-card-left { .route-card .name {
border-top-left-radius: 45px;
border-bottom-left-radius: 45px;
display: grid; display: grid;
place-content: center; height: 100%;
align-content: center;
padding: 20px;
font-size: 28px;
} }
.route-card-left img { .route-card .name > * {
width: 100%; flex: 1;
max-width: 120px;
}
.route-card-center {
display: grid;
place-content: center;
font-weight: bold;
padding: 4px;
max-width: 100%;
text-overflow: ellipsis;
text-align: center;
} }
</style> </style>

View file

@ -1,17 +1,16 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_BACKEND_URL } from '$env/static/public';
export let route: App.Route; export let route: App.Route;
</script> </script>
<a href="/routes/{route.id}"> <a href="/routes/{route.id}">
<div id="image" style:background-image="url({PUBLIC_BACKEND_URL}{route.cover})" /> <div id="image" style="background-image: url(/images/test-1.jpg" />
<div id="path-holder"> <div id='path-holder'>
<p class="path-name">{route.title_it}</p> <div style='font-size: 20px;'>Percorso</div>
<p>{route.name}</p>
</div> </div>
<div id="duration-holder"> <div id='duration-holder'>
<div style="font-size: 15px;">Dislivello</div> <div style='font-size: 15px;'>Durata</div>
<p id="duration">{route.elevation_gain}</p> <p id="duration">{Math.floor(Number(route.duration) / 60)}'</p>
</div> </div>
</a> </a>
@ -21,18 +20,18 @@
color: black; color: black;
display: block; display: block;
box-shadow: 0 0 50px #ccc; box-shadow: 0 0 50px #ccc;
border-radius: 5px; border-radius: 30px;
width: calc((100% - 50px)); width: calc((100% - 50px) / 2);
font-size: 18px; font-size: 18px;
} }
#image { #image {
width: 100%; width: 100%;
height: 140px; height: 150px;
object-position: center; object-position: center;
background-position: center; background-position: center;
object-fit: cover; object-fit: cover;
border-radius: 5px; border-radius: 30px;
border-bottom-right-radius: 0px; border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px; border-bottom-left-radius: 0px;
} }
@ -60,14 +59,6 @@
padding-top: 2px; padding-top: 2px;
padding-bottom: 2px; padding-bottom: 2px;
font-weight: bold; font-weight: bold;
font-size: 1em; font-size: 22px;
line-height: 1em;
text-overflow: ellipsis;
overflow: hidden;
}
.path-name {
view-transition-name: title;
max-height: 2em;
} }
</style> </style>

View file

@ -1,14 +1,12 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { dev } from '$app/environment';
let visible = dev ? false : true;
let time = 2000;
let visible = false;
let time = 0;
onMount(() => { onMount(() => {
if (dev) return; time = 0;
setTimeout(() => { setTimeout(() => {
visible = false; visible = false;
}, time); }, time);
@ -16,32 +14,15 @@
</script> </script>
{#if visible} {#if visible}
<img class="background" transition:fade src="/splash.webp" alt="splash" /> <img transition:fade src="/splash.webp" alt="splash" />
<div class="wrapper">
<img transition:fade class="logo" src="/images/splash-logo.png" alt="splash-logo" />
</div>
{/if} {/if}
<style> <style>
.background { img {
position: absolute; position: absolute;
z-index: 3; z-index: 99;
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0 auto; margin: 0 auto;
} }
.logo {
max-width: 100%;
height: auto;
z-index: 4;
}
.wrapper {
position: absolute;
display: grid;
place-content: center;
height: 100dvh;
width: 100dvw;
}
</style> </style>

View file

@ -1,57 +1,53 @@
<script lang="ts"> <script lang="ts">
import InfoTabTrekking from './tabs/InfoTabTrekking.svelte'; import InfoTab from './tabs/InfoTab.svelte';
import InfoTabBike from './tabs/InfoTabBike.svelte';
import DescTab from './tabs/DescTab.svelte'; import DescTab from './tabs/DescTab.svelte';
import MapTab from './tabs/MapTab.svelte'; import MapTab from './tabs/MapTab.svelte';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher } from 'svelte';
export let route: App.Route;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let ref: HTMLDivElement; let component = InfoTab;
let component = InfoTabTrekking; const activeClicked = (evt) => {
const clickedTab = evt.target.closest('button');
onMount(() => { for (const active of clickedTab.parentElement.querySelectorAll('.active')) {
console.log(route); active.classList.remove('active');
}); }
const dispatchClick = (c: InfoTabTrekking | InfoTabBike | DescTab | MapTab) => {
component = c; clickedTab.classList.add('active');
dispatch('tab-click', { component });
switch (clickedTab.getAttribute('id')) {
case 'info':
component = InfoTab;
break;
case 'desc':
component = DescTab;
break;
case 'map':
component = MapTab;
break;
}
dispatch('map-click', {component});
}; };
</script> </script>
<div id="tabs"> <!-- svelte-ignore a11y-click-events-have-key-events -->
<button <!-- svelte-ignore a11y-no-static-element-interactions -->
class:active={component === InfoTabTrekking}
on:click={() => dispatchClick(InfoTabTrekking)}
id="info-trekking"
>
<img src="/trekking.svg" alt="trekking" />
</button>
<button
class:active={component === InfoTabBike}
on:click={() => dispatchClick(InfoTabBike)}
id="info-bike"
>
<img src="/bike.svg" alt="bike" />
</button>
<button class:active={component === DescTab} on:click={() => dispatchClick(DescTab)} id="desc">
Descrizione</button
>
<button class:active={component === MapTab} on:click={() => dispatchClick(MapTab)} id="map">
Mappa</button
>
</div>
<div id="container"> <div id="container">
<div class="tab" bind:this={ref} role="tab"> <div id="tabs" on:click={activeClicked}>
<svelte:component this={component} {route} /> <button id="info" class="active">Info</button>
<button id="desc">Descrizione</button>
<button id="map">Mappa</button>
</div>
<div class="tab">
<svelte:component this={component} />
</div> </div>
</div> </div>
<style> <style>
#container { #container {
background-color: white; background-color: white;
height: 100%; height: calc(20vh);
} }
#tabs { #tabs {
display: flex; display: flex;
@ -73,13 +69,12 @@
padding: 15px; padding: 15px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
cursor: pointer; cursor: pointer;
} }
.tab { .tab {
padding: 10px; padding: 10px;
background-color: white; background-color: white;
overflow-y: scroll;
height: 100%;
} }
.active { .active {
@ -87,11 +82,4 @@
font-weight: bold !important; font-weight: bold !important;
border-bottom: 1px solid black !important; border-bottom: 1px solid black !important;
} }
#info-bike,
#info-trekking {
display: flex;
justify-content: center;
align-items: center;
}
</style> </style>

View file

@ -1,45 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
export let message = "L'applicazione è pronta per funzionare offline!";
let show = false;
let nodeRef: HTMLDivElement;
onMount(() => {
setTimeout(() => {
show = true;
setTimeout(() => {
show = false;
setTimeout(() => {
nodeRef?.parentNode?.removeChild(nodeRef);
}, 5000);
}, 5000);
}, 0);
});
</script>
<div bind:this={nodeRef} id="toast" class:show>
<img src="/icons/checkmark.svg" alt="check" />
<div>{message}</div>
</div>
<style>
.show {
transform: translateY(0vh) !important;
}
#toast {
transform: translateY(10vh);
transition: transform ease 0.4s;
position: fixed;
height: 10vh;
display: flex;
bottom: 0;
z-index: 4;
left: 0;
width: 100%;
justify-content: center;
background-color: #f6ae04;
color: black;
place-items: center;
}
</style>

View file

@ -1,15 +1,14 @@
<script lang="ts">
import { fade } from 'svelte/transition';
export let route: App.Route;
</script>
<div> <div>
{route.description_it} The standard Lorem Ipsum passage, used since the 1500s "Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum."
</div> </div>
<style> <style>
div { div {
padding: 10px; padding: 10px;
} }
</style> </style>

View file

@ -0,0 +1,76 @@
<script>
</script>
<div id="grid">
<div>
<p>Distanza</p>
<p>Value</p>
</div>
<div>
<p>Dislivello Positivo</p>
<p>Value</p>
</div>
<div>
<p>Velocità Media</p>
<p>Value</p>
</div>
<div>
<p>Difficolta</p>
<p>Value</p>
</div>
<div>
<p>Dislivello Negativo</p>
<p>Value</p>
</div>
<div>
<p>Durata</p>
<p>Value</p>
</div>
<div>
<p>Altitudine max</p>
<p>Value</p>
</div>
<div>
<p>Altitudine min</p>
<p>Value</p>
</div>
<div>
<p>Tipo Percorso</p>
<p>Value</p>
</div>
</div>
<style>
#grid {
display: grid;
grid-template-rows: repeat(3, 33%);
grid-template-columns: repeat(3, 1fr);
width: 100%;
background: white;
place-items: center;
font-size: 14px;
}
#grid div {
margin: 0 auto;
display: flex;
text-align: center;
flex-direction: column;
}
#grid div > p {
padding: 10px;
margin: 0px;
}
#grid div p:nth-child(2) {
font-weight: bold;
font-size: 16px;
}
</style>

View file

@ -1,77 +0,0 @@
<script lang="ts">
export let route: App.Route;
</script>
<div id="grid">
<div>
<p>Distanza</p>
<p>{route.route_sport_details[1].distance} m</p>
</div>
<div>
<p>Dislivello Positivo</p>
<p>{route.route_sport_details[1].elevation_gain}</p>
</div>
<div>
<p>Velocità Media</p>
<p>N/A</p>
</div>
<div>
<p>Difficolta</p>
<p>{route.route_sport_details[1].difficulty_it}</p>
</div>
<div>
<p>Dislivello Negativo</p>
<p>{route.route_sport_details[1].elevation_loss}</p>
</div>
<div>
<p>Durata</p>
<p>{route.route_sport_details[1].duration}'</p>
</div>
<div>
<p>Altitudine max</p>
<p>{route.route_sport_details[1].altitude_max}</p>
</div>
<div>
<p>Altitudine min</p>
<p>{route.route_sport_details[1].altitude_min}</p>
</div>
<div>
<p>Tipo Percorso</p>
<p>{route.route_sport_details[1].route_type_it}</p>
</div>
</div>
<style>
#grid {
display: grid;
grid-template-rows: repeat(3, 33%);
grid-template-columns: repeat(3, 1fr);
width: 100%;
background: white;
place-items: center;
font-size: 14px;
}
#grid div {
margin: 0 auto;
display: flex;
text-align: center;
flex-direction: column;
}
#grid div > p {
padding: 10px;
margin: 0px;
}
#grid div p:nth-child(2) {
font-weight: bold;
font-size: 16px;
}
</style>

View file

@ -1,77 +0,0 @@
<script lang="ts">
export let route: App.Route;
</script>
<div id="grid">
<div>
<p>Distanza</p>
<p>{route.route_sport_details[0].distance} m</p>
</div>
<div>
<p>Dislivello Positivo</p>
<p>{route.route_sport_details[0].elevation_gain}</p>
</div>
<div>
<p>Velocità Media</p>
<p>N/A</p>
</div>
<div>
<p>Difficolta</p>
<p>{route.route_sport_details[0].difficulty_it}</p>
</div>
<div>
<p>Dislivello Negativo</p>
<p>{route.route_sport_details[0].elevation_loss}</p>
</div>
<div>
<p>Durata</p>
<p>{route.route_sport_details[0].duration}'</p>
</div>
<div>
<p>Altitudine max</p>
<p>{route.route_sport_details[0].altitude_max}</p>
</div>
<div>
<p>Altitudine min</p>
<p>{route.route_sport_details[0].altitude_min}</p>
</div>
<div>
<p>Tipo Percorso</p>
<p>{route.route_sport_details[0].route_type_it}</p>
</div>
</div>
<style>
#grid {
display: grid;
grid-template-rows: repeat(3, 33%);
grid-template-columns: repeat(3, 1fr);
width: 100%;
background: white;
place-items: center;
font-size: 14px;
}
#grid div {
margin: 0 auto;
display: flex;
text-align: center;
flex-direction: column;
}
#grid div > p {
padding: 10px;
margin: 0px;
}
#grid div p:nth-child(2) {
font-weight: bold;
font-size: 16px;
}
</style>

View file

@ -1,132 +1,93 @@
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import { fade } from 'svelte/transition'; import { fade, fly } from 'svelte/transition';
const mapMarkerIcon = new URL('/icons/map.png', import.meta.url).href;
export let route;
const pianelloCoordinates = [43.14, 12.53]; const pianelloCoordinates = [43.14, 12.53];
let mapElement: string | HTMLElement; let mapElement: string | HTMLElement;
let leaflet; let leaflet;
let leafletGPX;
let map; let map;
let layerGroup; let layerGroup;
let latitude; let latitude;
let longitude; let longitude;
let accuracy; let accuracy;
let watchPositionId: number; const errorMessage = "Geolocation not available";
const errorMessage = 'Geolocation not available';
const attribution =
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
const openStreetMapTile = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
let errored = false; let errored = false;
const watchPosition = async () => { const watchPosition = async () => {
if (!('geolocation' in navigator)) { if (!("geolocation" in navigator)) {
errored = true; errored = true;
throw new Error(errorMessage); throw new Error(errorMessage)
} }
const options = { const options = {
enableHighAccuracy: true, enableHighAccuracy: true,
timeout: 5000, timeout: 5000,
maximumAge: 0 maximumAge: 0,
}; };
function success(pos: GeolocationPosition) { function success(pos) {
const crd = pos.coords; const crd = pos.coords;
let string = ''; let string = "";
latitude = crd.latitude; latitude = crd.latitude;
longitude = crd.longitude; longitude = crd.longitude;
accuracy = crd.accuracy; accuracy = crd.accuracy;
string += "Your current position is:";
string += `Latitude : ${crd.latitude}`;
string += `Longitude: ${crd.longitude}`;
string += `More or less ${crd.accuracy} meters.`;
}
const icon = leaflet.icon({ function error(err) {
iconUrl: mapMarkerIcon, console.warn(`ERROR(${err.code}): ${err.message}`);
iconSize: [75, 75] // size of the icon }
});
userMarker = leaflet.marker([latitude, longitude], { icon }).addTo(map);
// move the map to have the location in its center navigator.geolocation.watchPosition(success, error, options);
map.panTo(userMarker.getLatLng()); };
string += 'Your current position is:';
string += `Latitude : ${crd.latitude}`;
string += `Longitude: ${crd.longitude}`;
string += `More or less ${crd.accuracy} meters.`;
}
function error(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
}
watchPositionId = navigator.geolocation.watchPosition(success, error, options);
};
const renderMap = () => {
layerGroup = leaflet.layerGroup();
// Startup Map
map = leaflet.map(mapElement, {
//dragging: leaflet.Browser.mobile,
//tap: leaflet.Browser.mobile,
});
map.setView(pianelloCoordinates, 13);
leaflet.tileLayer(openStreetMapTile, { attribution }).addTo(map);
};
const renderGPX = () => {
const gpx = '/gpx/tidone.gpx'; // URL to your GPX file or the GPX itself
new leafletGPX(gpx, { async: true })
.on('loaded', function (e) {
map.fitBounds(e.target.getBounds());
})
.addTo(map);
};
onMount(async () => { onMount(async () => {
await watchPosition(); await watchPosition();
leaflet = await import('leaflet'); leaflet = await import('leaflet');
const { GPX } = await import('leaflet-gpx'); layerGroup = leaflet.layerGroup();
leafletGPX = GPX;
renderMap(); // Startup Map
renderGPX(); map = leaflet.map(mapElement, {
dragging: leaflet.Browser.mobile,
tap: leaflet.Browser.mobile,
minZoom: 12
});
map.setView(pianelloCoordinates, 13);
map.setMaxBounds(map.getBounds());
leaflet
.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
.addTo(map);
}); });
onDestroy(async () => { onDestroy(async () => {
if (watchPositionId) {
navigator.geolocation.clearWatch(watchPositionId);
}
if (map) { if (map) {
console.log('Unloading Leaflet map.'); console.log('Unloading Leaflet map.');
map.remove(); map.remove();
} }
}); });
</script> </script>
{#if !errored} {#if !errored}
<div id="map" in:fade bind:this={mapElement} /> <div id="map" in:fade bind:this={mapElement} />
{:else} {:else}
{errorMessage} {errorMessage}
{/if} {/if}
<svelte:head>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
</svelte:head>
<style> <style>
@import 'leaflet/dist/leaflet.css';
#map { #map {
height: 100%; height: calc(45vh + 60px);
z-index: 0; z-index: 0;
} }
</style> </style>

View file

@ -1,6 +1,12 @@
let latitude; let latitude;
let longitude; let longitude;
onmessage = () => {};
export {}; onmessage = () => {
};
export { };

View file

@ -1,93 +0,0 @@
[
{
"id": 1,
"sequence_number": 2,
"name_it": "Cimitero di Pianello Val Tidone",
"name_en": "Pianello Val Tidone Cemetery",
"description_it": "Il terrazzo fluviale alla confluenza tra Tidone e Chiarone \u00e8 stato fin dall\u2019antichit\u00e0 un luogo privilegiato per l\u2019insediamento umano. \r\n\r\nI materiali conservati presso il Museo Archeologico della Val Tidone evidenziano una continuit\u00e0 di vita dal II sec. a.C. al IV sec. d.C. \r\n\r\nIn particolare, in et\u00e0 romana \u00e8 testimoniata la presenza di un abitato di medie dimensioni, sulle cui strutture, cadute ormai in disuso, si impianta una necropoli altomedievale.",
"description_en": "The river terrace at the confluence between Tidone and Chiarone has been a privileged place for human settlement since ancient times.\r\n\r\nThe materials preserved at the Archaeological Museum of Val Tidone highlight a continuity of life from the 2nd century. B.C. to the 4th century. A.D.\r\n\r\nIn particular, in the Roman age there is evidence of the presence of a medium-sized settlement, on whose structures, which had now fallen into disuse, an early medieval necropolis was established.",
"latitude": 44.948232,
"longitude": 9.446436,
"created_at": "2023-11-02T11:10:48.000000Z",
"updated_at": null,
"deleted_at": null
},
{
"id": 2,
"sequence_number": 3,
"name_it": "Museo Archeologico della Val Tidone ",
"name_en": "Archaeological Museum of Val Tidone",
"description_it": "Il Museo Archeologico della Val Tidone accoglie e valorizza reperti provenienti dall\u2019intera vallata, raccontando la Storia della presenza umana e le trasformazioni del territorio dalla Preistoria al Medioevo. Il Museo \u00e8 ospitato nei sotterranei della Rocca Dal Verme, ricostruita dopo il passaggio del Barbarossa nel 1164. Prende il nome dal condottiero veneto Jacopo dal Verme, che qui fu infeudato dai Visconti di Milano, creando il cosiddetto Stato Vermesco. Nei sotterranei \u00e8 esposta anche un\u2019opera dell\u2019artista locale Paolo Vincenzo Novara, originario di Borgonovo V.T., ma che visse e lavor\u00f2 sempre a Pianello V.T. Si tratta di una veduta del borgo pianellese e delle colline circostanti.",
"description_en": "The Archaeological Museum of Val Tidone welcomes and valorises finds from the entire valley, telling the history of human presence and the transformations of the territory from Prehistory to the Middle Ages. The Museum is housed in the basement of the Rocca Dal Verme, rebuilt after the passage of Barbarossa in 1164. It takes its name from the Venetian leader Jacopo dal Verme, who was enfeoffed here by the Visconti of Milan, creating the so-called Vermesco State. In the basement there is also a work by the local artist Paolo Vincenzo Novara, originally from Borgonovo V.T., but who always lived and worked in Pianello V.T. This is a view of the Pianella village and the surrounding hills.",
"latitude": 44.948232,
"longitude": 9.40559,
"created_at": "2023-11-02T11:10:48.000000Z",
"updated_at": null,
"deleted_at": null
},
{
"id": 3,
"sequence_number": 1,
"name_it": "Arcello",
"name_en": "Arcello",
"description_it": "Qui sorgeva una villa romana con ambienti decorati. A fine Ottocento fu rinvenuta una stele funeraria intitolata a Caio Birrivs Mascvlvs, ora a Piacenza. Citato in un documento dell\u2019844, nel 1089 c\u2019era sicuramente un castello, poi distrutto dal Barbarossa e dal Pallavicino: rimangono il basamento della torre e le cantine dell\u2019attuale canonica. Del convento dei Carmelitani, dismesso nel 1652, rimane una torre. Il pittoresco borgo si sviluppa su una terrazza naturale prospiciente la Media Val Tidone e deve la sua importanza per essere stato feudo della nobile famiglia degli Arcelli dal 11 al 14 sec.",
"description_en": "Here stood a Roman villa with decorated rooms. At the end of the 19th century, a funerary stele dedicated to Caio Birrivs Mascvlvs was found, now in Piacenza. Mentioned in a document from 844, in 1089 there was certainly a castle, later destroyed by Barbarossa and Pallavicino: the base of the tower and the cellars of the current rectory remain. Of the Carmelite convent, decommissioned in 1652, only one tower remains. The picturesque village develops on a natural terrace overlooking the Middle Val Tidone and owes its importance to having been a fiefdom of the noble Arcelli family from the 11th to the 14th century.",
"latitude": 44.952267,
"longitude": 9.446436,
"created_at": "2023-11-02T11:20:30.000000Z",
"updated_at": null,
"deleted_at": null
},
{
"id": 4,
"sequence_number": 4,
"name_it": "Case Rebuffi",
"name_en": "Case Rebuffi",
"description_it": "Numerosi rinvenimenti archeologici del secolo scorso confermano che almeno in et\u00e0 romana qui doveva passare una strada: ormai perduti sono i reperti della sepoltura rinvenuta nel 1928. \r\n\r\nUn tale Rebuffum da Peccoraria compare nel Registrum Magnum di Piacenza quando nel 1187 riceve l\u2019investitura feudale di alcuni terreni: il nome del luogo potrebbe riferirsi a questo antenato. \r\n\r\nIl piccolo oratorio dedicato a S. Andrea di Avellino, ora privato, \u00e8 del XVIII secolo.",
"description_en": "Numerous archaeological discoveries from the last century confirm that at least in Roman times a road must have passed here: the remains of the burial found in 1928 are now lost.\r\n\r\nSuch a Rebuffum from Peccoraria appears in the Registrum Magnum of Piacenza when in 1187 he received the feudal investiture of some land: the name of the place could refer to this ancestor.\r\n\r\nThe small oratory dedicated to S. Andrea di Avellino, now private, dates back to the 18th century.",
"latitude": 44.937276,
"longitude": 9.384442,
"created_at": "2023-11-02T11:22:46.000000Z",
"updated_at": null,
"deleted_at": null
},
{
"id": 7,
"sequence_number": 5,
"name_it": "Casanova",
"name_en": "Casanova",
"description_it": "Borgo rurale sul versante sinistro della vallata e antico possedimento dei Dal Verme. Rinvenimenti archeologici testimoniano la presenza di un insediamento di et\u00e0 romana. Il borgo \u00e8 citato in documenti del X secolo come parte di un beneficio concesso dal monastero di S. Colombano di Bobbio. Particolare \u00e8 la presenza di due chiese: la prima, ormai in rovina, \u00e8 di origine medievale, e mostra ancora il campanile dotato di megafono d\u2019allarme. La seconda, dedicata a Santa\u202fMaria Assunta, fu edificata tra il 1733 e il 1749 per volont\u00e0 del conte Federico Dal Verme. L'edificio, a navata unica, \u00e8 caratterizzato da una facciata a capanna e da un ampio sagrato. L\u2019interno custodisce, sull'altare maggiore, un quadro raffigurante l'Assunzione\u202fdi autore ignoto.",
"description_en": "Rural village on the left side of the valley and ancient possession of the Dal Verme family. Archaeological finds testify to the presence of a Roman settlement. The village is mentioned in documents from the 10th century as part of a benefit granted by the monastery of S. Colombano di Bobbio. The presence of two churches is particular: the first, now in ruins, is of medieval origin, and still shows the bell tower equipped with an alarm megaphone. The second, dedicated to Santa Maria Assunta, was built between 1733 and 1749 by order of Count Federico Dal Verme. The building, with a single nave, is characterized by a gabled fa\u00e7ade and a large churchyard. The interior houses, on the main altar, a painting depicting the Assumption by an unknown artist.",
"latitude": 44.927274,
"longitude": 9.377744,
"created_at": "2023-11-02T11:23:28.000000Z",
"updated_at": null,
"deleted_at": null
},
{
"id": 8,
"sequence_number": 6,
"name_it": "Montemartino",
"name_en": "Montemartino",
"description_it": "Il nome del luogo, che ricorda un Santo legato ai longobardi, viene citato in un documento scritto nel X secolo nel Monastero di Bobbio. Nel XIV secolo qui esisteva un castello di propriet\u00e0 dei Da Fontana, le cui strutture sono visibili nel borgo, e la prima chiesa dedicata a S. Bartolomeo, poi rimaneggiata all\u2019inizio del XX secolo.",
"description_en": "The name of the place, which recalls a saint linked to the Lombards, is mentioned in a document written in the 10th century in the Monastery of Bobbio. In the 14th century there was a castle here owned by the Da Fontana family, whose structures are visible in the village, and the first church dedicated to S. Bartolomeo, then remodeled at the beginning of the 20th century.",
"latitude": 44.904475,
"longitude": 9.353009,
"created_at": "2023-11-02T11:23:28.000000Z",
"updated_at": null,
"deleted_at": null
},
{
"id": 9,
"sequence_number": 7,
"name_it": "Vallerenzo",
"name_en": "Vallerenzo",
"description_it": "Il nome del luogo sembra collegarsi al romano Valerius, nome di famiglia romana presente anche nella stele di Valeria Nardis, conservata al Museo Archeologico della Val Tidone, e sulla Tabula Alimentaria di Veleia. In un documento del 1033 compare un luogo detto Valarinci o Valerinci, mentre nel Registrum Magnum di Piacenza un atto del 1219 \u00e8 redatto proprio in Vallarencio. \r\n\r\nL\u2019oratorio del XVIII secolo, recentemente restaurato, \u00e8 dedicato alla B.V. della Misericordia e S. Lodovico.",
"description_en": "The name of the place seems to be connected to the Roman Valerius, a Roman family name also present in the stele of Valeria Nardis, preserved in the Archaeological Museum of Val Tidone, and on the Tabula Alimentaria of Veleia. In a document from 1033 a place called Valarinci or Valerinci appears, while in the Registrum Magnum of Piacenza an act from 1219 is drawn up precisely in Vallarencio.\r\n\r\nThe recently restored 18th century oratory is dedicated to the B.V. della Misericordia and S. Lodovico.",
"latitude": 44.891136,
"longitude": 9.372671,
"created_at": "2023-11-02T11:27:17.000000Z",
"updated_at": null,
"deleted_at": null
}
]

View file

@ -1,78 +0,0 @@
{
"id": 1,
"name_it": "GLI INSEDIAMENTI ANTICHI LUNGO IL TIDONE",
"name_en": "GLI INSEDIAMENTI ANTICHI LUNGO IL TIDONE",
"description_it": "Il percorso pone l\u2019attenzione su diversi borghi situati lungo il corso del Tidone. L\u2019insediamento umano in Val Tidone \u00e8 testimoniato fin dalle epoche pi\u00f9 remote, quando l\u2019uomo, in base alle necessit\u00e0 delle varie epoche, sceglie terrazzi fluviali o luoghi pi\u00f9 riparati per impiantare i propri siti abitati. \r\n\r\nL\u2019itinerario partendo da Arcello, passando per Pianello V.T., e risalendo il corso del torrente fino a Vallerenzo, incontra numerosi borghi, dove rinvenimenti archeologici testimoniano la presenza romana e lo sfruttamento agricolo dell\u2019area. In epoca successiva, la costruzione di chiese, castelli e monasteri fa comprendere l\u2019importanza strategica del territorio durante tutto il Medioevo, nelle lotte per il potere e come snodo degli itinerari di commercio e di pellegrinaggio.",
"description_en": "The route focuses on various villages located along the Tidone. Human settlement in Val Tidone has been witnessed since the most remote times, when man, based on the needs of the various eras, chose river terraces or more sheltered places to establish his own inhabited sites.\r\n\r\nThe itinerary starting from Arcello, passing through Pianello V.T., and going up the course of the stream to Vallerenzo, encounters numerous villages, where archaeological finds testify to the Roman presence and the agricultural exploitation of the area. In the subsequent era, the construction of churches, castles and monasteries makes us understand the strategic importance of the territory throughout the Middle Ages, in the struggles for power and as a hub for trade and pilgrimage itineraries.",
"route_category_id": 2,
"created_at": "2023-11-02T10:50:07.000000Z",
"updated_at": null,
"deleted_at": null,
"route_sport_details": [
{
"id": 1,
"route_id": 1,
"sport_id": 1,
"short_description_it": "Percorso escursionistico intermedio. Buon allenamento richiesto. Sentieri facilmente percorribili. Adatto a ogni livello di abilit\u00e0. ",
"short_description_en": "Intermediate hiking route. Good training required. Easily accessible paths. Suitable for all skill levels.",
"gpx_path": "",
"distance": 16800,
"duration": 288,
"elevation_gain": 439,
"elevation_loss": null,
"altitude_max": 620,
"altitude_min": 180,
"difficulty_it": "Facile",
"difficulty_en": "Easy",
"route_type_it": "Percorso ad anello",
"route_type_en": "Ring route",
"created_at": "2023-11-02T10:57:41.000000Z",
"updated_at": null,
"deleted_at": null,
"sport": {
"id": 1,
"name_it": "Trekking",
"name_en": "Trekking",
"description_it": "Percorso escursionistico",
"description_en": "Trekking route",
"icon": "",
"created_at": "2023-10-31T18:37:30.000000Z",
"updated_at": null,
"deleted_at": null
}
},
{
"id": 2,
"route_id": 1,
"sport_id": 2,
"short_description_it": "Giro in bici per esperti. Ottimo allenamento richiesto. Superfici perlopi\u00f9 asfaltate. Adatto a ogni livello di abilit\u00e0. ",
"short_description_en": "Bike ride for experts. Excellent training required. Mostly asphalted surfaces. Suitable for all skill levels.",
"gpx_path": "-",
"distance": 16800,
"duration": 84,
"elevation_gain": 333,
"elevation_loss": null,
"altitude_max": 51,
"altitude_min": 181,
"difficulty_it": "Facile",
"difficulty_en": "Easy",
"route_type_it": "Percorso andata/ritorno",
"route_type_en": "Round trip route",
"created_at": "2023-11-02T11:01:55.000000Z",
"updated_at": null,
"deleted_at": null,
"sport": {
"id": 2,
"name_it": "Cicloturismo",
"name_en": "Cycle tourism",
"description_it": "Percorso da fare in bicicletta",
"description_en": "Cycling route",
"icon": "",
"created_at": "2023-10-31T18:37:30.000000Z",
"updated_at": null,
"deleted_at": null
}
}
],
"pictures": []
}

View file

@ -1,49 +0,0 @@
[
{
"id": 1,
"title_it": "GLI INSEDIAMENTI ANTICHI LUNGO IL TIDONE",
"title_en": "GLI INSEDIAMENTI ANTICHI LUNGO IL TIDONE",
"description_it": "Il percorso pone l\u2019attenzione su diversi borghi situati lungo il corso del Tidone. L\u2019insediamento umano in Val Tidone \u00e8 testimoniato fin dalle epoche pi\u00f9 remote, quando l\u2019uomo, in base alle necessit\u00e0 delle varie epoche, sceglie terrazzi fluviali o luoghi pi\u00f9 riparati per impiantare i propri siti abitati. \r\n\r\nL\u2019itinerario partendo da Arcello, passando per Pianello V.T., e risalendo il corso del torrente fino a Vallerenzo, incontra numerosi borghi, dove rinvenimenti archeologici testimoniano la presenza romana e lo sfruttamento agricolo dell\u2019area. In epoca successiva, la costruzione di chiese, castelli e monasteri fa comprendere l\u2019importanza strategica del territorio durante tutto il Medioevo, nelle lotte per il potere e come snodo degli itinerari di commercio e di pellegrinaggio.",
"description_en": "The route focuses on various villages located along the Tidone. Human settlement in Val Tidone has been witnessed since the most remote times, when man, based on the needs of the various eras, chose river terraces or more sheltered places to establish his own inhabited sites.\r\n\r\nThe itinerary starting from Arcello, passing through Pianello V.T., and going up the course of the stream to Vallerenzo, encounters numerous villages, where archaeological finds testify to the Roman presence and the agricultural exploitation of the area. In the subsequent era, the construction of churches, castles and monasteries makes us understand the strategic importance of the territory throughout the Middle Ages, in the struggles for power and as a hub for trade and pilgrimage itineraries.",
"length": 33600,
"elevation_gain": 772,
"sports": [
{ "id": 1, "name_it": "Trekking", "name_en": "Trekking", "duration": 288 },
{ "id": 2, "name_it": "Cicloturismo", "name_en": "Cycle tourism", "duration": 84 }
],
"cover": null
},
{
"id": 2,
"title_it": "GLI ARTISTI DEL 900 A PIANELLO VAL TIDONE",
"title_en": "GLI ARTISTI DEL 900 A PIANELLO",
"description_it": "L\u2019itinerario vuole far conoscere l\u2019arte e gli artisti che nel \u2018900 hanno operato in Val Tidone e si sono lasciati ispirare da questo territorio. Si tratta di artisti piacentini, talvolta provenienti proprio dalla Val Tidone, come Franco Corradini originario di Borgonovo, e addirittura dal paese di Pianello V.T., come \u00e8 il caso di Paolo Vincenzo Novara, che abit\u00f2 e lavor\u00f2 nel borgo. \r\n\r\nLa maggior parte del percorso si svolge a Pianello V.T., dove si visitano alcuni spazi della Rocca Dal Verme, il monumento ai Caduti, una cappella del Cimitero, la Chiesa parrocchiale, la Cappella di Lourdes e un mistadello ovvero una piccola cappella votiva. \r\n\r\nAl di fuori del paese, due tappe del percorso portano alla Rocca d\u2019Olgisio e all\u2019oratorio di Roccapulzana. ",
"description_en": "The itinerary aims to raise awareness of the art and artists who worked in Val Tidone in the 1900s and were inspired by this area. These are artists from Piacenza, sometimes coming from Val Tidone, such as Franco Corradini originally from Borgonovo, and even from the town of Pianello V.T., as is the case of Paolo Vincenzo Novara, who lived and worked in the village.\r\n\r\nMost of the route takes place in Pianello V.T., where you visit some spaces of the Rocca Dal Verme, the war memorial, a chapel in the cemetery, the parish church, the Lourdes chapel and a mistadello or a small votive chapel.\r\n\r\nOutside the town, two stages of the route lead to the Rocca d'Olgisio and the oratory of Roccapulzana.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"cover": null
},
{
"id": 3,
"title_it": "NELLE TERRE DI CUNIZA",
"title_en": "IN CUNIZA'S LAND",
"description_it": "Ricca di rinvenimenti archeologici, la Val Tidone fu frequentata sin dal Paleolitico e fu densamente abitata per tutta la sua storia, in particolare nell\u2019Alto Medioevo, per il quale abbiamo le testimonianze archeologiche ma anche parti di edifici ancora conservati. Notizie si ricavano dai nomi dei luoghi e dai documenti antichi. Tra questi, fondamentale \u00e8 il documento di vendita del 1033 in cui Cuniza, donna di legge longobarda, vende alcuni terreni dell\u2019area collinare di Pianello: questo ci permette di ricostruire la fisionomia di alcuni luoghi e di tracciare una possibile strada che congiungeva Pianello con Travo e Bobbio. Il percorso percorre in parte questa strada e passa nei luoghi di propriet\u00e0 della famiglia di Cuniza, non dimenticando il Museo Archeologico, che conserva reperti del periodo.",
"description_en": "Rich in archaeological finds, Val Tidone was frequented since the Paleolithic and was densely inhabited throughout its history, particularly in the Early Middle Ages, for which we have archaeological evidence but also parts of buildings still preserved. Information is obtained from place names and ancient documents. Among these, fundamental is the sales document from 1033 in which Cuniza, a woman of Lombard law, sells some land in the hilly area of Pianello: this allows us to reconstruct the physiognomy of some places and to trace a possible road that connected Pianello with Travo and Bobbio. The route partly follows this road and passes through places owned by the Cuniza family, not forgetting the Archaeological Museum, which preserves finds from the period.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"cover": null
},
{
"id": 4,
"title_it": "NEI LUOGHI DEI PARTIGIANI",
"title_en": "IN THE PLACES OF THE PARTISANS",
"description_it": "A partire dall\u2019autunno del \u201843 si costitu\u00ec a Piacenza il Comitato di Liberazione antifascista che organizz\u00f2 gruppi armati di resistenza. L\u2019Alta e Media Val Tidone rappresent\u00f2 uno dei territori cardine della lotta partigiana piacentina e fu teatro di alcuni episodi che hanno segnato, purtroppo anche in modo negativo, come nel caso dell\u2019eccidio di Str\u00e0, questa parte di storia piacentina. Il nostro territorio vide la presenza di alcuni gruppi armati di grande importanza, con alcuni dei personaggi pi\u00f9 famosi nella storia della Liberazione piacentina, come il \u2018Fausto\u2019, il \u2018Valoroso\u2019, il \u2018Ballonaio\u2019. Pianello e la Rocca d\u2019Olgisio, insieme all\u2019alta Valle del Tidoncello e l\u2019alta Val Luretta, ebbero un ruolo attivo nei fatti svoltisi qui tra l\u2019inizio del \u201844 e la Liberazione.",
"description_en": "Starting from the autumn of 1943, the anti-fascist Liberation Committee was formed in Piacenza and organized armed resistance groups. The Upper and Middle Val Tidone represented one of the key territories of the Piacenza partisan struggle and was the scene of some episodes that marked, unfortunately also in a negative way, as in the case of the Str\u00e0 massacre, this part of Piacenza history. Our territory saw the presence of some armed groups of great importance, with some of the most famous characters in the history of the Piacenza Liberation, such as the 'Fausto', the 'Valoroso', the 'Ballonaio'. Pianello and the Rocca d'Olgisio, together with the upper Tidoncello Valley and the upper Luretta Valley, played an active role in the events that took place here between the beginning of '44 and the Liberation.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"cover": null
}
]

View file

@ -1,41 +0,0 @@
[
{
"id": 1,
"name_it": "Natura",
"name_en": "Nature",
"description_it": "Giri nella natura",
"description_en": "Countryside routes",
"icon": "",
"color": "#de0e1b",
"cover": "/montagne.png",
"created_at": null,
"updated_at": null,
"deleted_at": null
},
{
"id": 2,
"name_it": "Storia",
"name_en": "History",
"description_it": "Giri nella storia",
"description_en": "History routes",
"icon": "",
"color": "#f6ae04",
"cover": "/archi.png",
"created_at": null,
"updated_at": null,
"deleted_at": null
},
{
"id": 3,
"name_it": "Enogastronomia e tradizione",
"name_en": "Tradition, food and wine",
"description_it": "Giri nella tradizione locale",
"description_en": "Culinarian and traditional routes",
"icon": "",
"color": "#213c8b",
"cover": "/bibbito.png",
"created_at": null,
"updated_at": null,
"deleted_at": null
}
]

View file

@ -1,161 +0,0 @@
[
{
"id": 1,
"title_it": "GLI INSEDIAMENTI ANTICHI LUNGO IL TIDONE",
"title_en": "GLI INSEDIAMENTI ANTICHI LUNGO IL TIDONE",
"description_it": "Il percorso pone l\u2019attenzione su diversi borghi situati lungo il corso del Tidone. L\u2019insediamento umano in Val Tidone \u00e8 testimoniato fin dalle epoche pi\u00f9 remote, quando l\u2019uomo, in base alle necessit\u00e0 delle varie epoche, sceglie terrazzi fluviali o luoghi pi\u00f9 riparati per impiantare i propri siti abitati. \r\n\r\nL\u2019itinerario partendo da Arcello, passando per Pianello V.T., e risalendo il corso del torrente fino a Vallerenzo, incontra numerosi borghi, dove rinvenimenti archeologici testimoniano la presenza romana e lo sfruttamento agricolo dell\u2019area. In epoca successiva, la costruzione di chiese, castelli e monasteri fa comprendere l\u2019importanza strategica del territorio durante tutto il Medioevo, nelle lotte per il potere e come snodo degli itinerari di commercio e di pellegrinaggio.",
"description_en": "The route focuses on various villages located along the Tidone. Human settlement in Val Tidone has been witnessed since the most remote times, when man, based on the needs of the various eras, chose river terraces or more sheltered places to establish his own inhabited sites.\r\n\r\nThe itinerary starting from Arcello, passing through Pianello V.T., and going up the course of the stream to Vallerenzo, encounters numerous villages, where archaeological finds testify to the Roman presence and the agricultural exploitation of the area. In the subsequent era, the construction of churches, castles and monasteries makes us understand the strategic importance of the territory throughout the Middle Ages, in the struggles for power and as a hub for trade and pilgrimage itineraries.",
"length": 33600,
"elevation_gain": 772,
"sports": [
{ "id": 1, "name_it": "Trekking", "name_en": "Trekking" },
{ "id": 2, "name_it": "Cicloturismo", "name_en": "Cycle tourism" }
],
"category_id": 2,
"category_name_it": "Storia",
"category_name_en": "History"
},
{
"id": 2,
"title_it": "GLI ARTISTI DEL 900 A PIANELLO VAL TIDONE",
"title_en": "GLI ARTISTI DEL 900 A PIANELLO",
"description_it": "L\u2019itinerario vuole far conoscere l\u2019arte e gli artisti che nel \u2018900 hanno operato in Val Tidone e si sono lasciati ispirare da questo territorio. Si tratta di artisti piacentini, talvolta provenienti proprio dalla Val Tidone, come Franco Corradini originario di Borgonovo, e addirittura dal paese di Pianello V.T., come \u00e8 il caso di Paolo Vincenzo Novara, che abit\u00f2 e lavor\u00f2 nel borgo. \r\n\r\nLa maggior parte del percorso si svolge a Pianello V.T., dove si visitano alcuni spazi della Rocca Dal Verme, il monumento ai Caduti, una cappella del Cimitero, la Chiesa parrocchiale, la Cappella di Lourdes e un mistadello ovvero una piccola cappella votiva. \r\n\r\nAl di fuori del paese, due tappe del percorso portano alla Rocca d\u2019Olgisio e all\u2019oratorio di Roccapulzana. ",
"description_en": "The itinerary aims to raise awareness of the art and artists who worked in Val Tidone in the 1900s and were inspired by this area. These are artists from Piacenza, sometimes coming from Val Tidone, such as Franco Corradini originally from Borgonovo, and even from the town of Pianello V.T., as is the case of Paolo Vincenzo Novara, who lived and worked in the village.\r\n\r\nMost of the route takes place in Pianello V.T., where you visit some spaces of the Rocca Dal Verme, the war memorial, a chapel in the cemetery, the parish church, the Lourdes chapel and a mistadello or a small votive chapel.\r\n\r\nOutside the town, two stages of the route lead to the Rocca d'Olgisio and the oratory of Roccapulzana.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 2,
"category_name_it": "Storia",
"category_name_en": "History"
},
{
"id": 3,
"title_it": "NELLE TERRE DI CUNIZA",
"title_en": "IN CUNIZA'S LAND",
"description_it": "Ricca di rinvenimenti archeologici, la Val Tidone fu frequentata sin dal Paleolitico e fu densamente abitata per tutta la sua storia, in particolare nell\u2019Alto Medioevo, per il quale abbiamo le testimonianze archeologiche ma anche parti di edifici ancora conservati. Notizie si ricavano dai nomi dei luoghi e dai documenti antichi. Tra questi, fondamentale \u00e8 il documento di vendita del 1033 in cui Cuniza, donna di legge longobarda, vende alcuni terreni dell\u2019area collinare di Pianello: questo ci permette di ricostruire la fisionomia di alcuni luoghi e di tracciare una possibile strada che congiungeva Pianello con Travo e Bobbio. Il percorso percorre in parte questa strada e passa nei luoghi di propriet\u00e0 della famiglia di Cuniza, non dimenticando il Museo Archeologico, che conserva reperti del periodo.",
"description_en": "Rich in archaeological finds, Val Tidone was frequented since the Paleolithic and was densely inhabited throughout its history, particularly in the Early Middle Ages, for which we have archaeological evidence but also parts of buildings still preserved. Information is obtained from place names and ancient documents. Among these, fundamental is the sales document from 1033 in which Cuniza, a woman of Lombard law, sells some land in the hilly area of Pianello: this allows us to reconstruct the physiognomy of some places and to trace a possible road that connected Pianello with Travo and Bobbio. The route partly follows this road and passes through places owned by the Cuniza family, not forgetting the Archaeological Museum, which preserves finds from the period.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 2,
"category_name_it": "Storia",
"category_name_en": "History"
},
{
"id": 4,
"title_it": "NEI LUOGHI DEI PARTIGIANI",
"title_en": "IN THE PLACES OF THE PARTISANS",
"description_it": "A partire dall\u2019autunno del \u201843 si costitu\u00ec a Piacenza il Comitato di Liberazione antifascista che organizz\u00f2 gruppi armati di resistenza. L\u2019Alta e Media Val Tidone rappresent\u00f2 uno dei territori cardine della lotta partigiana piacentina e fu teatro di alcuni episodi che hanno segnato, purtroppo anche in modo negativo, come nel caso dell\u2019eccidio di Str\u00e0, questa parte di storia piacentina. Il nostro territorio vide la presenza di alcuni gruppi armati di grande importanza, con alcuni dei personaggi pi\u00f9 famosi nella storia della Liberazione piacentina, come il \u2018Fausto\u2019, il \u2018Valoroso\u2019, il \u2018Ballonaio\u2019. Pianello e la Rocca d\u2019Olgisio, insieme all\u2019alta Valle del Tidoncello e l\u2019alta Val Luretta, ebbero un ruolo attivo nei fatti svoltisi qui tra l\u2019inizio del \u201844 e la Liberazione.",
"description_en": "Starting from the autumn of 1943, the anti-fascist Liberation Committee was formed in Piacenza and organized armed resistance groups. The Upper and Middle Val Tidone represented one of the key territories of the Piacenza partisan struggle and was the scene of some episodes that marked, unfortunately also in a negative way, as in the case of the Str\u00e0 massacre, this part of Piacenza history. Our territory saw the presence of some armed groups of great importance, with some of the most famous characters in the history of the Piacenza Liberation, such as the 'Fausto', the 'Valoroso', the 'Ballonaio'. Pianello and the Rocca d'Olgisio, together with the upper Tidoncello Valley and the upper Luretta Valley, played an active role in the events that took place here between the beginning of '44 and the Liberation.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 2,
"category_name_it": "Storia",
"category_name_en": "History"
},
{
"id": 5,
"title_it": "SULLE TRACCE DELLA GALEINA GRISA",
"title_en": "ON THE FOOTSTEPS OF GALEINA GRISA",
"description_it": "Nella notte della Galeina Grisa, tra il 30 aprile e il primo di maggio \u00e8 tradizione che giovani e vecchi si riuniscano per raggiungere le principali localit\u00e0 della valle per una lunga nottata di festeggiamenti, durante la quale le compagnie di cantori itineranti visitano osterie e cascine proponendo i loro canti goliardici inneggianti alla primavera. In cambio, essi ottengono spuntini a base di salame, uova e cipollotti (i cosiddetti \u2018bavaroni\u2019 in espressione dialettale) annaffiati da abbondante vino rosso, spesso servito in un\u2019unica coppa condivisa da tutti. \r\n\r\nIl percorso ripercorre i luoghi della tradizione che sono soliti accogliere i cantori e profumare di ospitalit\u00e0 e di convivialit\u00e0 agreste, ma consente anche di godere numerosi punti panoramici.",
"description_en": "On the night of Galeina Grisa, between April 30th and May 1st, it is traditional for young and old to come together to reach the main towns of the valley for a long night of celebrations, during which companies of itinerant singers visit taverns and farmhouses proposing their playful songs praising spring. In exchange, they receive snacks based on salami, eggs and spring onions (the so-called 'bavaroni' in dialect) washed down with abundant red wine, often served in a single cup shared by all.\r\n\r\nThe route retraces the traditional places that usually welcome the singers and smell of hospitality and rural conviviality, but also allows you to enjoy numerous panoramic points.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 3,
"category_name_it": "Enogastronomia e tradizione",
"category_name_en": "Tradition, food and wine"
},
{
"id": 6,
"title_it": "PERCORSO BOTTEGHE STORICHE",
"title_en": "HISTORICAL WORKSHOPS ROUTE",
"description_it": "Un rilassante percorso urbano da effettuare a piedi o in bicicletta, tra le prelibate offerte enogastronomiche del paese; in ognuna delle tappe si possono trovare i prodotti tipici della tradizione piacentina e valtidonese ed essere coccolati dalle atmosfere antiche e dai profumi delle varie botteghe paesane. Un tour per golosi ma anche per chi vuole portare con s\u00e9 uno squisito ricordo della giornata in Val Tidone. \r\n\r\nLa bellissima piazza porticata di Pianello e le sue strette viuzze colorate accolgono inoltre numerosi bar che propongono aperitivi, cocktails, gelati e frapp\u00e8, che non potranno che rendere ancora pi\u00f9 piacevole la visita al paese.",
"description_en": "A relaxing urban route to be taken on foot or by bicycle, among the delicious food and wine offerings of the town; in each of the stages you can find the typical products of the Piacenza and Valtidone tradition and be pampered by the ancient atmospheres and scents of the various village shops. A tour for gourmands but also for those who want to take with them an exquisite memory of the day in Val Tidone.\r\n\r\nThe beautiful porticoed square of Pianello and its narrow colorful streets also host numerous bars offering aperitifs, cocktails, ice creams and milkshakes, which will only make a visit to the town even more pleasant.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 3,
"category_name_it": "Enogastronomia e tradizione",
"category_name_en": "Tradition, food and wine"
},
{
"id": 7,
"title_it": "UN ANELLO DI CRINALI E COLORI",
"title_en": "A RING OF RIDGES AND COLOURS",
"description_it": "Dal cimitero di Arcello si parte in direzione sud, mantenendo alle spalle il paese. Dopo circa 300 m lungo la strada asfaltata, si arriva in prossimit\u00e0 della Tenuta Santa Giustina. Qui si imbocca il bivio a destra e da qui si continua a camminare lungo un percorso ad anello che si sviluppa lungo il crinale, salendo gradualmente fino al punto pi\u00f9 alto di questo percorso panoramico. Da qui comincia la discesa, attraverso il bosco di Santa Giustina, fino a raggiungere il lato posteriore della Tenuta. Il percorso si ricongiunge poi chiudendo l\u2019anello in prossimit\u00e0 del primo bivio incontrato. Da qui si riprende la strada asfaltata per tornare al cimitero di Arcello.",
"description_en": "From the Arcello cemetery you set off in a southerly direction, keeping the town behind you. After about 300 m along the asphalt road, you arrive near the Tenuta Santa Giustina. Here you take the fork on the right and from here you continue walking along a circular route that develops along the ridge, gradually climbing up to the highest point of this panoramic route. From here the descent begins, through the Santa Giustina forest, until reaching the back side of the estate. The route then rejoins, closing the ring near the first crossroads encountered. From here we take the asphalt road back to the Arcello cemetery.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 1,
"category_name_it": "Natura",
"category_name_en": "Nature"
},
{
"id": 8,
"title_it": "LUNGO LE SPONDE DEL TIDONE",
"title_en": "ALONG THE BANKS OF THE TIDONE",
"description_it": "Partendo da Casa Nova si percorre la strada asfaltata per un breve tratto, in direzione Pradaglia. Dopo circa 400 m si svolta a destra, scendendo verso il Tidone costeggiando un vigneto. Arrivati in prossimit\u00e0 del torrente, si attraversa il letto del torrente in prossimit\u00e0 del guado, raggiungendo la sponda sinistra del torrente stesso (Comune Alta Val Tidone). Si prosegue in direzione sinistra lungo il Sentiero del Tidone (direzione Sorgente) per circa 2 Km. Superato il Mulino del Ceppetto si guada di nuovo il torrente, per tornate sulla sponda destra. Da qui si risale verso Casa Barbieri, si imbocca una stradina di ghiaia in direzione Pradaglia. Attraversata questa localit\u00e0, si prosegue in direzione Casa Nova per richiudere l\u2019anello, lungo quest\u2019ultimo tratto di strada asfaltata a bassa percorrenza.",
"description_en": "Starting from Casa Nova, follow the asphalt road for a short stretch, towards Pradaglia. After about 400 m, turn right, descending towards the Tidone along a vineyard. Once you arrive near the stream, cross the bed of the stream near the ford, reaching the left bank of the stream itself (Alta Val Tidone Municipality). Continue in a left direction along the Sentiero del Tidone (direction Sorgente) for about 2 km. After passing the Mulino del Ceppetto you cross the stream again, to return to the right bank. From here you go up towards Casa Barbieri, take a gravel road towards Pradaglia. Once you have crossed this location, continue in the direction of Casa Nova to close the ring, along this last stretch of low-traffic asphalt road.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 1,
"category_name_it": "Natura",
"category_name_en": "Nature"
},
{
"id": 9,
"title_it": "DOVE LO SPIRITO AGRESTE INCONTRA LA GENUINIT\u00c0 DI UN CONTESTO RURALE",
"title_en": "WHERE THE RURAL SPIRIT MEETS THE GENUINITY OF A RURAL CONTEXT",
"description_it": "Partendo da Casa Nova si raggiunge Localit\u00e0 La Scabbia lungo un tragitto di 700 m. Da qui il percorso si sviluppa lungo un anello che sale lungo il versante est della vallata, raggiungendo la chiesetta di Madonna del Sasso e continua poi fino a raggiungere il crinale in Localit\u00e0 Gabbiano. Avanzando in costa, mantenendo la Rocca d\u2019Olgisio davanti a sinistra, si inizia la discesa lungo lo stesso versante, passando questa volta per localit\u00e0 Carbonara, e poi via via si scende tra vigneti, campi di grano e sentieri ombreggiati fino ad arrivare di nuovo in Localit\u00e0 La Scabbia, dove l\u2019anello si chiude. Da qui si ritorna a Casa Nova, percorrendo di nuovo il tragitto iniziale di 700 m.",
"description_en": "Starting from Casa Nova you reach Localit\u00e0 La Scabbia along a 700 m journey. From here the route develops along a ring that climbs along the eastern side of the valley, reaching the small church of Madonna del Sasso and then continues until reaching the ridge in Localit\u00e0 Gabbiano. Advancing along the coast, keeping the Rocca d'Olgisio in front on the left, you begin the descent along the same side, this time passing through Carbonara, and then gradually descend through vineyards, wheat fields and shaded paths until you arrive again in Localit\u00e0 La Scabbia, where the ring closes. From here you return to Casa Nova, following the initial 700 m journey again.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 1,
"category_name_it": "Natura",
"category_name_en": "Nature"
},
{
"id": 10,
"title_it": "LE ARENARIE TRA I BOSCHI DELLA VAL CHIARONE",
"title_en": "THE SANDSTONES AMONG THE WOODS OF THE CHIARONE VALLEY",
"description_it": "Il percorso comincia in prossimit\u00e0 dell\u2019inizio del sentiero CAI 209, lungo la Val Chiarone. Imboccando tale sentiero si inizia a salire, fino a raggiungere un boschetto all\u2019interno del quale si sviluppa gran parte del percorso. Dopo circa 1 Km si interseca la bretella che raggiunge la piana di S. Martino. Questa bretella \u00e8 un ampio sentiero pianeggiante che si imbocca sulla sinistra. Attraversata la piana di S. Martino si riprende il sentiero CAI 211che porta al caratteristico Becco del Merlo, un corridoio tra due importanti rocce, lungo circa 20 m. Il sentiero prosegue ad anello ricongiungendosi al bivio dove si \u00e8 imboccata la bretella per la Piana di S. Martino. Da qui si imbocca la discesa, ripercorrendo il percorso gi\u00e0 fatto in precedenza lungo il sentiero CAI 209.",
"description_en": "The route begins near the start of the CAI 209 path, along the Val Chiarone. Taking this path you begin to climb until you reach a grove within which much of the route develops. After about 1 km the road that reaches the S. Martino plain intersects. This link road is a wide flat path that you take on the left. After crossing the S. Martino plain, take the CAI 211 path again which leads to the characteristic Becco del Merlo, a corridor between two important rocks, approximately 20 m long. The path continues in a ring, rejoining the crossroads where you took the slip road to the Piana di S. Martino. From here take the descent, retracing the route already taken previously along the CAI 209 path.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 1,
"category_name_it": "Natura",
"category_name_en": "Nature"
},
{
"id": 11,
"title_it": "NATURA AUTENTICA SUL MONTE SERENO",
"title_en": "AUTHENTIC NATURE ON MONTE SERENO",
"description_it": "Il percorso si sviluppa lungo l\u2019anello contrassegnato dal sentiero CAI 225. Partendo da Ca\u2019 del Fabbro, si imbocca il sentiero, dopo circa 200 m si svola a sinistra e si inizia a salire lungo il versante, lungo un percorso sdrucciolevole caratterizzato da ghiaione. Arrivati sul crinale, dove ci sono diversi punti in cui fioriscono le orchidee tipiche dell\u2019Appennino nella prima met\u00e0 di maggio, si prosegue mantenendo il sentiero CAI225, fino a entrare nel bosco. Qui ci sono un paio di bivi a cui prestare attenzione. Ad un certo punto il sentiero si apre su una radura, il monte Sereno, da cui si pu\u00f2 vedere la Val Tebbia. Da qui si comincia a scendere, attraversando ancora tratti boschivi, alternati a brevi mulattiere. L\u2019ultima parte del percorso \u00e8 fitta di vegetazione, bisogna prestare attenzione ai segnali e presenta alcune criticit\u00e0 nella discesa: il sentiero si restringe e la ripidit\u00e0 aumenta. Arrivati nel fondovalle, si imbocca la strada asfaltata per richiudere l\u2019anello in localit\u00e0 C\u00e0 del Fabbro.",
"description_en": "The route develops along the ring marked by the CAI path 225. Starting from Ca' del Fabbro, take the path, after about 200 m you turn left and start climbing along the slope, along a slippery path characterized by scree . Once you reach the ridge, where there are several points where the typical orchids of the Apennines bloom in the first half of May, continue keeping the CAI225 path until you enter the woods. There are a couple of crossroads to watch out for here. At a certain point the path opens onto a clearing, Mount Sereno, from which you can see Val Tebbia. From here you begin to descend, still crossing wooded sections, alternating with short mule tracks. The last part of the route is thick with vegetation, you must pay attention to the signs and presents some critical issues in the descent: the path narrows and the steepness increases. Once you reach the valley floor, take the asphalt road to close the ring in the C\u00e0 del Fabbro area.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 1,
"category_name_it": "Natura",
"category_name_en": "Nature"
},
{
"id": 12,
"title_it": "L\u2019AGRICOLTURA IN VAL TIDONE, VIGNETI, ULIVI E CAMPI COLTIVATI",
"title_en": "AGRICULTURE IN VAL TIDONE, VINEYARDS, OLIVE TREES AND CULTIVATED FIELDS",
"description_it": "Dal cimitero di Arcello si parte in direzione sud, mantenendo alle spalle il paese. Dopo circa 300 m lungo la strada asfaltata, si arriva in prossimit\u00e0 della Tenuta Santa Giustina. Qui si imbocca il bivio a destra e da qui si continua a camminare lungo il crinale. Dopo circa 2 Km si incontra un bivio: proseguendo a sinistra continua il percorso N2, mantenendo la destra si continua sul percorso T1. La carraia inizia a scendere dolcemente, fino alla localit\u00e0 rurale di Poggio Cavalli. Si imbocca qui la piccola strada asfaltata che, sempre in discesa, porta fino al Podere Ca\u2019 Nova, dove termina il percorso. Quest\u2019ultimo tratto \u00e8 circondato da vigneti e incontra uno degli uliveti della Val Tidone.",
"description_en": "From the Arcello cemetery you set off in a southerly direction, keeping the town behind you. After about 300 m along the asphalt road, you arrive near the Tenuta Santa Giustina. Here you take the fork on the right and from here you continue walking along the ridge. After about 2 km you come to a crossroads: continuing on the left, continue on route N2, keeping to the right, continue on route T1. The carriage road begins to descend gently, up to the rural town of Poggio Cavalli. Here you take the small asphalt road which, always downhill, leads to Podere Ca' Nova, where the route ends. This last stretch is surrounded by vineyards and meets one of the olive groves of Val Tidone.",
"length": 0,
"elevation_gain": 0,
"sports": [],
"category_id": 1,
"category_name_it": "Natura",
"category_name_en": "Nature"
}
]

View file

@ -1,35 +0,0 @@
{
"id": 1,
"name_it": "GLI INSEDIAMENTI ANTICHI LUNGO IL TIDONE",
"name_en": "GLI INSEDIAMENTI ANTICHI LUNGO IL TIDONE",
"description_it": "Il percorso pone l\u2019attenzione su diversi borghi situati lungo il corso del Tidone. L\u2019insediamento umano in Val Tidone \u00e8 testimoniato fin dalle epoche pi\u00f9 remote, quando l\u2019uomo, in base alle necessit\u00e0 delle varie epoche, sceglie terrazzi fluviali o luoghi pi\u00f9 riparati per impiantare i propri siti abitati. \r\n\r\nL\u2019itinerario partendo da Arcello, passando per Pianello V.T., e risalendo il corso del torrente fino a Vallerenzo, incontra numerosi borghi, dove rinvenimenti archeologici testimoniano la presenza romana e lo sfruttamento agricolo dell\u2019area. In epoca successiva, la costruzione di chiese, castelli e monasteri fa comprendere l\u2019importanza strategica del territorio durante tutto il Medioevo, nelle lotte per il potere e come snodo degli itinerari di commercio e di pellegrinaggio.",
"description_en": "The route focuses on various villages located along the Tidone. Human settlement in Val Tidone has been witnessed since the most remote times, when man, based on the needs of the various eras, chose river terraces or more sheltered places to establish his own inhabited sites.\r\n\r\nThe itinerary starting from Arcello, passing through Pianello V.T., and going up the course of the stream to Vallerenzo, encounters numerous villages, where archaeological finds testify to the Roman presence and the agricultural exploitation of the area. In the subsequent era, the construction of churches, castles and monasteries makes us understand the strategic importance of the territory throughout the Middle Ages, in the struggles for power and as a hub for trade and pilgrimage itineraries.",
"route_category_id": 2,
"created_at": "2023-11-02T10:50:07.000000Z",
"updated_at": null,
"deleted_at": null,
"route_sport_details": [
{
"id": 1,
"route_id": 1,
"sport_id": 1,
"short_description_it": "Percorso escursionistico intermedio. Buon allenamento richiesto. Sentieri facilmente percorribili. Adatto a ogni livello di abilit\u00e0. ",
"short_description_en": "Intermediate hiking route. Good training required. Easily accessible paths. Suitable for all skill levels.",
"gpx_path": "",
"distance": 16800,
"duration": 288,
"elevation_gain": 439,
"elevation_loss": null,
"altitude_max": 620,
"altitude_min": 180,
"difficulty_it": "Facile",
"difficulty_en": "Easy",
"route_type_it": "Percorso ad anello",
"route_type_en": "Ring route",
"created_at": "2023-11-02T10:57:41.000000Z",
"updated_at": null,
"deleted_at": null
}
],
"pictures": []
}

View file

@ -1,60 +0,0 @@
import { PUBLIC_BACKEND_URL } from '$env/static/public';
const API_URL = `${PUBLIC_BACKEND_URL}/api`;
const getAllRoutes = async () => {
let data = [];
const response = await fetch(`${API_URL}/all-routes`);
const json = await response.json();
data = json;
return data;
};
const getRouteCategories = async () => {
let data = [];
const response = await fetch(`${API_URL}/route-categories`);
const json = await response.json();
data = json;
return data;
};
const getRouteByCategory = async (categoryId: number) => {
let data = [];
const response = await fetch(`${API_URL}/route-by-category/${categoryId}`);
const json = await response.json();
data = json;
return data;
};
const getRoute = async (routeId: number) => {
let data = {};
const response = await fetch(`${API_URL}/route/${routeId}`);
const json = await response.json();
data = json;
return data;
};
const getSport = async (routeId: number, sportId: number) => {
let data = {};
const response = await fetch(`${API_URL}/route/${routeId}/${sportId}`);
const json = await response.json();
data = json;
return data;
};
const getPlacemarks = async (routeId: number, sportId: number) => {
let data = {};
const response = await fetch(`${API_URL}/getPlacemarks/${routeId}/${sportId}`);
const json = await response.json();
data = json;
return data;
};
export { getAllRoutes, getPlacemarks, getSport, getRoute, getRouteByCategory, getRouteCategories };

View file

@ -1,45 +0,0 @@
<script>
import { page } from '$app/stores';
</script>
<div id="container">
<div class="wrapper">
<p id="status">{$page.status}</p>
<p id="message">{$page.error?.message}</p>
</div>
</div>
<style>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
#container {
background-image: url(/splash.webp);
background-size: cover;
display: grid;
place-content: center;
height: 100dvh;
width: 100dvw;
z-index: 99;
position: absolute;
font-size: 24px;
font-family: monospace;
}
.wrapper {
background: transparent;
display: flex;
backdrop-filter: blur(5px);
border-radius: 10px;
border: 1px solid black;
}
#status {
border-right: 1px solid black;
}
p {
padding: 20px;
}
</style>

View file

@ -1,3 +0,0 @@
export const prerender = false;
export const ssr = true;
export const csr = true;

View file

@ -1,66 +1,42 @@
<script lang="ts"> <script>
import { pwaInfo } from 'virtual:pwa-info';
import { onMount } from 'svelte'
import BottomAppBar from '$lib/components/BottomAppBar.svelte'; import BottomAppBar from '$lib/components/BottomAppBar.svelte';
import Toast from '$lib/components/Toast.svelte'; $: webManifestLink = pwaInfo ? pwaInfo.webManifest.linkTag : ''
import { onMount } from 'svelte'; export const prerender = true;
import { onNavigate } from '$app/navigation';
import { dev } from '$app/environment'; onMount(async () => {
if (pwaInfo) {
const { registerSW } = await import('virtual:pwa-register')
registerSW({
immediate: true,
onRegistered(r) {
console.log(`SW Registered: ${r}`)
console.log(JSON.stringify(r));
},
onRegisterError(error) {
console.log('SW registration error', error)
}
})
}
})
</script>
let node: HTMLElement; <slot></slot>
<footer>
<BottomAppBar />
</footer>
onMount(() => { {#await import('$lib/ReloadPrompt.svelte') then { default: ReloadPrompt}}
if (dev) return; <ReloadPrompt />
{/await}
if ('serviceWorker' in navigator) { <svelte:head>
addEventListener('load', function () { {@html webManifestLink}
navigator.serviceWorker.register('/service-worker.js'); </svelte:head>
});
if (BroadcastChannel) {
const channel = new BroadcastChannel('sw-messages');
channel.addEventListener('message', (event) => {
new Toast({ target: node });
});
} else {
navigator.serviceWorker.addEventListener('message', (evt) => {
new Toast({ target: node });
});
}
navigator.serviceWorker.ready.then((registration) => {
registration?.active?.postMessage('Save client');
});
}
});
onNavigate((navigation: { complete: any }) => {
if (!document.startViewTransition) return;
return new Promise<void>((resolve) => {
document?.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
});
</script>
<div bind:this={node}></div>
<main><slot /></main>
<BottomAppBar />
<style> <style>
main { footer {
height: calc(100dvh - 10dvh - 10px); background: white;
overflow-y: scroll;
width: 100%;
display: grid;
grid-template-rows: 70px auto;
}
div {
position: absolute;
width: 0;
height: 0;
} }
</style> </style>

View file

@ -1,47 +1,49 @@
<script lang="ts"> <script>
import BottomAppBar from '$lib/components/BottomAppBar.svelte';
import Path from '$lib/components/Path.svelte'; import Path from '$lib/components/Path.svelte';
import Splash from '$lib/components/Splash.svelte';
export let data;
let categories: App.Category[] = data.categories;
</script> </script>
<main> <Splash />
<header>
<div id="welcome-message">Benvenuti a <span class="bold">Pianello Val Tidone</span></div> <div id="welcome-message">Benvenuti a <span class="bold">Pianello Val Tidone</span></div>
</header>
<main>
<div id="route-cards"> <div id="route-cards">
{#each categories as category} <Path color="#de0e1b" type="naturalistici" />
<Path src={category.cover} color={category.color} name={category.name_it} id={category.id} /> <Path color="#f6ae04" type="storici" />
{/each} <Path color="#213c8b" type="tradizionalistici" />
</div> </div>
</main> </main>
<style> <style>
main { header {
text-align: center;
display: grid; display: grid;
margin: 0 auto; place-items: center;
height: calc(90dvh - 10px); }
main {
width: 100%; width: 100%;
height: 72vh;
margin: 0px;
font-family: 'Roboto-Regular'; font-family: 'Roboto-Regular';
grid-template-rows: 70px auto; display: grid;
place-items: center;
} }
.bold { .bold {
font-weight: bold; font-weight: bold;
font-size: 22px;
} }
#welcome-message { #welcome-message {
padding-top: 20px;
font-size: 18px; font-size: 18px;
display: grid;
width: 100%;
place-content: center;
text-align: center;
} }
#route-cards { #route-cards {
padding: 20px; display: flex;
display: grid; flex-direction: column;
width: 100%;
height: 100%; height: 100%;
gap: 10px; width: 100%;
align-items: center;
} }
</style> </style>

View file

@ -1,16 +0,0 @@
import { error } from '@sveltejs/kit';
import { PUBLIC_BACKEND_URL } from '$env/static/public';
const API_URL = `${PUBLIC_BACKEND_URL}/api`;
export async function load({ fetch }) {
let categories = [];
try {
const response = await fetch(`${API_URL}/route-categories`);
categories = await response.json();
} catch (ex) {
error(404, { message: 'API Not Found' });
}
return { categories };
}

View file

@ -0,0 +1,15 @@
import { error } from "@sveltejs/kit";
export async function load({ params }) {
return {
title: params.slug,
routes: [
{id: 1, name: 'Pianello 1', image: '/images/test-1.jpg', duration: 1233},
{id: 2, name: 'Pianello 2', image: '/images/test-1.jpg', duration: 1233},
{id: 3, name: 'Pianello 3', image: '/images/test-1.jpg', duration: 2134},
{id: 4, name: 'Pianello 4', image: '/images/test-1.jpg', duration: 2134 * 4},
{id: 5, name: 'Pianello 5', image: '/images/test-1.jpg', duration: 2134 * 4},
]
}
}

View file

@ -1,16 +1,15 @@
<script lang="ts"> <script lang="ts">
import Route from '$lib/components/Route.svelte'; import Route from '$lib/components/Route.svelte';
import HomeHeader from '$lib/components/HomeHeader.svelte'; import HomeHeader from '$lib/components/HomeHeader.svelte';
export let data; export let data;
</script> </script>
<HomeHeader title={data.category}></HomeHeader> <HomeHeader title={data.title}></HomeHeader>
<div> <div>
{#each data.routes as route} {#each data.routes as route}
<Route {route}></Route> <Route {route}></Route>
{/each} {/each}
</div> </div>
<style> <style>
@ -19,7 +18,9 @@
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
gap: 15px; gap: 15px;
margin-top: 70px;
padding-top: 10px; padding-top: 10px;
max-height: calc(100% - 20vh);
overflow-y: scroll; overflow-y: scroll;
} }
</style> </style>

View file

@ -1,26 +0,0 @@
import { error } from '@sveltejs/kit';
import { PUBLIC_BACKEND_URL } from '$env/static/public';
const API_URL = `${PUBLIC_BACKEND_URL}/api`;
export async function load({ params, fetch }) {
const response = await fetch(`${API_URL}/route-categories`);
const categories: App.Category[] = await response.json();
const categoryId = Number(params.slug);
const category: App.Category = categories.find((c) => c.id === categoryId) as App.Category;
if (!category) {
error(404, { message: 'Path not found' });
}
const response2 = await fetch(`${API_URL}/route-by-category/${categoryId}`);
const routes = await response2.json();
const toReturn = {
category: category.name_it,
routes
};
return toReturn;
}

View file

@ -0,0 +1,10 @@
import { error } from "@sveltejs/kit";
export async function load({ params }) {
return {
id: 1,
name: 'Pianello 1',
image: '/images/test-1.jpg',
duration: 1233
};
}

View file

@ -1,72 +1,72 @@
<script lang="ts"> <script lang="ts">
import Header from '$lib/components/Header.svelte'; import Header from '$lib/components/Header.svelte';
import Tabs from '$lib/components/Tabs.svelte'; import Tabs from '$lib/components/Tabs.svelte';
import MapTab from '$lib/components/tabs/MapTab.svelte'; import MapTab from '$lib/components/tabs/MapTab.svelte';
import { onMount } from 'svelte';
export let data: App.Route; let syncWorker: Worker | undefined = undefined;
let divider: HTMLDivElement; let latitude;
let isMap: boolean = false; let longitude;
let accuracy;
export let data;
let divider;
const tabClick = (event) => { const mapClick = (evt) => {
isMap = event.detail.component === MapTab; if(evt.detail.component === MapTab)
}; divider.style.transform = "translateY(calc(0vh + 70px))";
else
divider.style.transform = "translateY(30vh)";
}
</script> </script>
<Header></Header>
<img src="/images/splash-background.webp" alt="splash" />
<div bind:this={divider} id="divider" class:move-to-top={isMap}> <Header></Header>
<div id="banner"> <img src='/images/splash-background.webp' alt='splash'>
<p class="path-name">Percorso <b>{data.name_it}</b></p>
<p id="duration">Dislivello {data?.route_sport_details[0]?.elevation_gain} m</p> <div bind:this={divider} id='divider'>
</div> <div id='banner'>
<Tabs on:tab-click={tabClick} route={data}></Tabs> <p>Percorso <b>{data.name}</b></p>
<p id='duration'>Durata {Math.floor(Number(data.duration) / 60)}'</p>
</div>
<Tabs on:map-click={mapClick}></Tabs>
</div> </div>
<style> <style>
#divider { #divider {
background: white; background: white;
margin-top: 0px; margin-top: 0px;
margin-top: 0; margin-top: 0;
align-self: end; transform: translateY(30vh);
display: grid; transition: transform 400ms;
} }
.move-to-top { img {
align-self: start !important; width: 100%;
height: calc(90dvh - 80px); height: 100%;
grid-template-rows: auto auto 1fr; position: absolute;
} z-index: -1;
}
img { #banner {
width: 100%; display: flex;
height: calc(100% - 10vh - 10px); padding: 5px;
position: absolute; background-color: #de0e1b;
z-index: -1; color: white;
} font-size: 1.3em;
justify-content: space-evenly;
align-items: center;
padding: 20px;
}
#banner p {
margin: 0;
padding: 0;
}
#banner { #banner #duration {
display: flex; font-size: 14px;
padding: 5px; text-align: rightìì;
background-color: #de0e1b; }
color: white;
font-size: 1.3em;
justify-content: space-evenly;
align-items: center;
padding: 20px;
}
#banner p {
margin: 0;
padding: 0;
}
#banner #duration {
font-size: 14px;
text-align: rightìì;
}
.path-name {
view-transition-name: title;
}
</style> </style>

View file

@ -1,16 +0,0 @@
import { error } from '@sveltejs/kit';
import { PUBLIC_BACKEND_URL } from '$env/static/public';
const API_URL = `${PUBLIC_BACKEND_URL}/api`;
export async function load({ params, fetch }) {
const routeId = Number(params.slug);
const response = await fetch(`${API_URL}/route/${routeId}`);
const route = await response.json();
if (!route) {
error(404, { message: 'Route non found' });
}
return route;
}

View file

@ -1 +1 @@
Settings Settings

View file

@ -1,80 +0,0 @@
/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import { build, files, version, prerendered } from '$service-worker';
// Create a unique cache name for this deployment
const CACHE = `sw-cache-${version}`;
const ASSETS = [
...build, // the app itself
...files, // everything in `static`,
...prerendered // dynamic routes
];
let client;
addEventListener('message', (event) => {
client = event.source;
});
self.addEventListener('install', (event) => {
// Create a new cache and add all files to it
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
}
event.waitUntil(addFilesToCache());
});
self.addEventListener('activate', (event) => {
// Remove previous cached data from disk
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}
event.waitUntil(deleteOldCaches());
let channel;
if (BroadcastChannel) {
channel = new BroadcastChannel('sw-messages');
channel.postMessage({ title: 'Cache Downloaded' });
} else {
client.postMessage('Cache Downloaded');
}
});
self.addEventListener('fetch', (event) => {
// ignore POST requests etc
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
return cache.match(url.pathname);
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch {
return cache.match(event.request);
}
}
event.respondWith(respond());
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M620-740q-33 0-56.5-23.5T540-820q0-33 23.5-56.5T620-900q33 0 56.5 23.5T700-820q0 33-23.5 56.5T620-740ZM432-540l88 92v248h-80v-200L312-512q-14-12-19-25t-5-31q0-18 5.5-30.5T312-624l112-112q13-13 27.5-18.5T484-760q18 0 32.5 5.5T544-736l76 76q27 27 63 43.5t81 16.5v80q-63 0-114-22.5T560-604l-32-32-96 96Zm-232 60q85 0 142.5 57.5T400-280q0 85-57.5 142.5T200-80q-85 0-142.5-57.5T0-280q0-85 57.5-142.5T200-480Zm0 340q57 0 98.5-41.5T340-280q0-57-41.5-98.5T200-420q-57 0-98.5 41.5T60-280q0 57 41.5 98.5T200-140Zm560-340q85 0 142.5 57.5T960-280q0 85-57.5 142.5T760-80q-85 0-142.5-57.5T560-280q0-85 57.5-142.5T760-480Zm0 340q57 0 98.5-41.5T900-280q0-57-41.5-98.5T760-420q-57 0-98.5 41.5T620-280q0 57 41.5 98.5T760-140Z"/></svg>

Before

Width:  |  Height:  |  Size: 813 B

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 42.875 8.625 C 42.84375 8.632813 42.8125 8.644531 42.78125 8.65625 C 42.519531 8.722656 42.292969 8.890625 42.15625 9.125 L 21.71875 40.8125 L 7.65625 28.125 C 7.410156 27.8125 7 27.675781 6.613281 27.777344 C 6.226563 27.878906 5.941406 28.203125 5.882813 28.597656 C 5.824219 28.992188 6.003906 29.382813 6.34375 29.59375 L 21.25 43.09375 C 21.46875 43.285156 21.761719 43.371094 22.050781 43.328125 C 22.339844 43.285156 22.59375 43.121094 22.75 42.875 L 43.84375 10.1875 C 44.074219 9.859375 44.085938 9.425781 43.875 9.085938 C 43.664063 8.746094 43.269531 8.566406 42.875 8.625 Z"/></svg>

Before

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1,46 +0,0 @@
{
"id": "/",
"name": "PianelloExperience",
"short_name": "PianelloExperience",
"start_url": "/",
"display": "standalone",
"background_color": "#fff",
"description": "PianelloExperience",
"icons": [
{
"src": "images/app-icon-48x48.jpeg",
"sizes": "48x48",
"type": "image/jpeg"
},
{
"src": "images/app-icon-72x72.jpeg",
"sizes": "72x72",
"type": "image/jpeg"
},
{
"src": "images/app-icon-96x96.jpeg",
"sizes": "96x96",
"type": "image/jpeg"
},
{
"src": "images/app-icon-144x144.jpeg",
"sizes": "144x144",
"type": "image/jpeg"
},
{
"src": "images/app-icon-168x168.jpeg",
"sizes": "168x168",
"type": "image/jpeg"
},
{
"src": "images/app-icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "images/app-icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m280-40 123-622q6-29 27-43.5t44-14.5q23 0 42.5 10t31.5 30l40 64q18 29 46.5 52.5T700-529v-71h60v560h-60v-406q-48-11-89-35t-71-59l-24 120 84 80v300h-80v-240l-84-80-72 320h-84Zm17-395-85-16q-16-3-25-16.5t-6-30.5l30-157q6-32 34-50.5t60-12.5l46 9-54 274Zm243-305q-33 0-56.5-23.5T460-820q0-33 23.5-56.5T540-900q33 0 56.5 23.5T620-820q0 33-23.5 56.5T540-740Z"/></svg>

Before

Width:  |  Height:  |  Size: 457 B

View file

@ -1,22 +1,19 @@
import adapter from '@sveltejs/adapter-node'; import adapter from "@sveltejs/adapter-node";
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
kit: { kit: {
files: {
serviceWorker: 'src/sw.ts'
},
serviceWorker: { serviceWorker: {
register: false register: false,
}, },
adapter: adapter({ adapter: adapter({
fallback: null, fallback: null,
precompress: true, precompress: true,
strict: true strict: true
}) }),
} },
}; };
export default config; export default config;

View file

@ -1,20 +1,50 @@
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from "@sveltejs/kit/vite";
import adapter from '@sveltejs/adapter-node'; import adapter from "@sveltejs/adapter-node";
import { SvelteKitPWA } from '@vite-pwa/sveltekit'
const config = { const config = {
plugins: [sveltekit()], plugins: [
test: { sveltekit(),
include: ['src/**/*.{test,spec}.{js,ts}'] SvelteKitPWA({
}, workbox: {
build: { globPatterns: ['**/*.{js,ts,css,html}'],
minify: 'esbuild', },
target: 'esnext' manifest: {
}, start_url: '/',
kit: { background_color: "#213c8b",
adapter: adapter({ theme_color: "#de0e1b",
precompress: false lang: 'it',
}) name: "PianelloExperience",
} display: "fullscreen",
short_name: "PianelloExperience",
description: "Pianello Experience",
icons: [
{
src: "/images/app-icon-512x512.png",
sizes: "512x512",
type: "image/png",
},
{
src: "/images/app-icon-192x192.png",
sizes: "192x192",
type: "image/png",
},
],
},
})
],
test: {
include: ["src/**/*.{test,spec}.{js,ts}"],
},
build: {
minify: "esbuild",
target: "esnext",
},
kit: {
adapter: adapter({
precompress: false,
})
},
}; };
export default config; export default config;

3993
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load diff