Compare commits

...

13 commits

14 changed files with 1033 additions and 31 deletions

59
about.html Normal file
View file

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./css/about.css" />
<link rel="shortcut icon" href="./assets/favicon@192.png" type="image/png">
<style>
body {
max-width: 800px;
padding: 15px;
box-sizing: border-box;
margin-left: auto;
margin-right: auto;
padding-top: 100px;
background: black;
color: white;
}
</style>
<title>À propos de la carte du Camp Interhack</title>
</head>
<body>
<section class="about">
<img src="./assets/favicon@512.png" alt="Interhack Camp">
<h1>À propos <small>de la carte du Camp Interhack</small></h1>
<p>
La carte de Camp à été élaborée par la commission Ambiance et Spacialisation.
On espère qu'elle vous sera utile.
</p>
<p>
Si vous avez un soucis, que vous avez trouvé un bug ou que vous voulez contribuer, vous pouvez vous rendre sur le <a href="https://git.interhacker.space/epickiwi/2026.camp.carte" target="_blank">GIT de l'interhack</a> et parciper à son amélioration.
Vous pouvez aussi trouver Kiwi qui se fera une joie de vous expliquer comment contribuer ou comment la carte fonctionne.
</p>
<hr>
<p>
Nous avons élaboré cette carte avec <a href="https://qgis.org/" target="_blank">QGIS</a>, un logiciel libre d'analyse et d'édition de cartes.
Pour la partie interactive sur le site, nous avons utilisé du HTML, du CSS et du JavaScript. Les icones ont été fabriquées sur <a href="https://inkscape.org/fr/" target="_blank">Inkscape</a>, les textures du fond de carte sur <a href="https://www.gimp.org/" target="_blank">Gimp</a> et les éléments 3D dans <a href="https://www.blender.org/" target="_blank">Blender</a>.
</p>
<hr>
<p>
Le code source de la carte est en <a href="https://www.gnu.org/licenses/agpl-3.0.fr.html" target="_blank">license libre AGPLv3</a>. Les données sont en license <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.fr" target="_blank">CC-BY-SA</a> et incluses dans le code source.
</p>
<p>
Le code source peut être téléchargé et redistribué librement: <a href="./carte-camp-code-source.tar.gz" download="carte-camp-code-source.tar.gz">Télécharger le code source</a>
</p>
</section>
</body>
</html>

680
assets/amenities-btn.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.2 MiB

View file

@ -3,8 +3,8 @@
"name": "batiments",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "id": 1, "nom": "Batiment A", "batiment": true }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -2.091581996371882, 47.541032945940522 ], [ -2.09151399426637, 47.541497984433285 ], [ -2.091312011285422, 47.541481979469303 ], [ -2.091326034403365, 47.541400730704773 ], [ -2.091241014242576, 47.541394942672135 ], [ -2.091251030755392, 47.541319968658911 ], [ -2.091334848934643, 47.541327974547109 ], [ -2.091346067428997, 47.541254515067628 ], [ -2.091304879528295, 47.541251918559851 ], [ -2.091309046397626, 47.541206046901102 ], [ -2.091353038921917, 47.541207940189693 ], [ -2.091375035184063, 47.541061913344244 ], [ -2.091330882395567, 47.541059965956265 ], [ -2.091335990817103, 47.541026008366721 ], [ -2.091380744596368, 47.54102704967903 ], [ -2.091381966610932, 47.541019003174263 ], [ -2.091581996371882, 47.541032945940522 ] ] ] ] } },
{ "type": "Feature", "properties": { "id": 2, "nom": "Batiment B", "batiment": true, "electricite-triphase": true }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -2.091630987136071, 47.541689935433055 ], [ -2.091803321239079, 47.54170457447416 ], [ -2.091796950736927, 47.541739951576218 ], [ -2.092355992350255, 47.541780954364874 ], [ -2.092339965929748, 47.54185403445365 ], [ -2.091958937782201, 47.541822011214833 ], [ -2.09193297498098, 47.54193998862457 ], [ -2.091763976376737, 47.541926005529561 ], [ -2.091756003232534, 47.541964993201951 ], [ -2.091590991200393, 47.541954999486393 ], [ -2.091630987136071, 47.541689935433055 ] ] ] ] } },
{ "type": "Feature", "properties": { "id": 10, "nom": "Toilettes sèches", "batiment": true }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -2.091342607190285, 47.541484412003662 ], [ -2.091438001950184, 47.541492274863295 ], [ -2.09143324403292, 47.541516949622384 ], [ -2.09133755253389, 47.541508679404679 ], [ -2.091342607190285, 47.541484412003662 ] ] ] ] } }
{ "type": "Feature", "properties": { "id": 1, "name": "Batiment A", "ignore": true, "batiment": true }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -2.091581996371882, 47.541032945940522 ], [ -2.09151399426637, 47.541497984433285 ], [ -2.091312011285422, 47.541481979469303 ], [ -2.091326034403365, 47.541400730704773 ], [ -2.091241014242576, 47.541394942672135 ], [ -2.091251030755392, 47.541319968658911 ], [ -2.091334848934643, 47.541327974547109 ], [ -2.091346067428997, 47.541254515067628 ], [ -2.091304879528295, 47.541251918559851 ], [ -2.091309046397626, 47.541206046901102 ], [ -2.091353038921917, 47.541207940189693 ], [ -2.091375035184063, 47.541061913344244 ], [ -2.091330882395567, 47.541059965956265 ], [ -2.091335990817103, 47.541026008366721 ], [ -2.091380744596368, 47.54102704967903 ], [ -2.091381966610932, 47.541019003174263 ], [ -2.091581996371882, 47.541032945940522 ] ] ] ] } },
{ "type": "Feature", "properties": { "id": 2, "name": "Batiment B", "ignore": true, "batiment": true, "electricite-triphase": true }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -2.091630987136071, 47.541689935433055 ], [ -2.091803321239079, 47.54170457447416 ], [ -2.091796950736927, 47.541739951576218 ], [ -2.092355992350255, 47.541780954364874 ], [ -2.092339965929748, 47.54185403445365 ], [ -2.091958937782201, 47.541822011214833 ], [ -2.09193297498098, 47.54193998862457 ], [ -2.091763976376737, 47.541926005529561 ], [ -2.091756003232534, 47.541964993201951 ], [ -2.091590991200393, 47.541954999486393 ], [ -2.091630987136071, 47.541689935433055 ] ] ] ] } },
{ "type": "Feature", "properties": { "id": 10, "name": "Toilettes sèches", "ignore": true, "batiment": true }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -2.091342607190285, 47.541484412003662 ], [ -2.091438001950184, 47.541492274863295 ], [ -2.09143324403292, 47.541516949622384 ], [ -2.09133755253389, 47.541508679404679 ], [ -2.091342607190285, 47.541484412003662 ] ] ] ] } }
]
}

46
css/about.css Normal file
View file

@ -0,0 +1,46 @@
.about {
font-family: sans-serif;
}
.about img[src="./assets/favicon@512.png"] {
margin-top: 50px;
margin-bottom: 50px;
height: 100px;
max-height: 50vw;
display: block;
margin-left: auto;
margin-right: auto;
filter: drop-shadow(currentColor 0px 0px 1px) drop-shadow(currentColor 0px 0px 1px) drop-shadow(currentColor 0px 0px 1px);
}
/*
#result-panel-dialog .about img[src="./assets/favicon@512.png"] {
display: none;
}*/
.about h1 {
font-weight: normal;
font-size: 2em;
text-align: center;
margin-top: 50px;
margin-bottom: 50px;
}
.about h1 small {
display: block;
font-size: 0.7em;
}
.about a {
color: inherit;
}
.about hr {
border: none;
height: 50px;
}
.about p {
line-height: 1.5;
}

View file

@ -13,6 +13,8 @@ body {
align-items: stretch;
min-height: 100vh;
background: black;
}
body > * {
@ -50,8 +52,17 @@ body > hr {
}
#main-header #logo {
max-height: clamp(2rem, 10vh, 5rem);
border: none;
background: none;
padding: 0;
margin: 0;
filter: drop-shadow(rgba(0,0,0,0.5) 0 0 10px);
cursor: pointer;
}
#main-header #logo img {
max-height: clamp(2rem, 10vh, 5rem);
display: block;
}
/* MAP */
@ -65,6 +76,10 @@ body > hr {
z-index: -1;
}
.leaflet-container {
background: #3f3f3f;
}
.leaflet-container .leaflet-control-attribution {
background: rgba(0, 0, 0, 0.5);
color: white;
@ -80,12 +95,13 @@ body > hr {
#map .highlight-point-icon > * {
width: fit-content;
height: fit-content;
transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
gap: 3px;
}
#map .highlight-point-icon > a {
@ -195,6 +211,22 @@ body > hr {
left: 5px;
}
@media screen and (max-width: 599.9px) {
#search-section:has(input:focus) {
position: fixed;
bottom: 10px;
left: 10px;
width: calc(100vw - 20px);
margin: 0;
height: min-content;
box-sizing: border-box;
}
}
#search-result {
min-height: 100vh;
}
#search-section input {
background: black;
font-size: 1em;
@ -526,4 +558,47 @@ camp-talk[data-track-id="34"] {
camp-talk.active {
animation: active-talk 1.5s alternate-reverse infinite linear;
}
/* EXPLORE NAV */
#explore {
position: absolute;
bottom: 10px;
right: 10px;
width: min-content;
}
#explore:has( ~ dialog:not([open])) {
position: fixed;
}
#explore details {
display: flex;
flex-direction: column-reverse;
align-items: center;
row-gap: 15px;
}
#explore details > summary {
display: block;
}
#explore details > summary img {
height: calc(20px + 2em + 2ex);
display: block;
}
#explore details button {
display: block;
border: none;
background: none;
padding: 0;
margin: 0;
cursor: pointer;
}
#explore details > button img {
height: 2.7em;
}

View file

@ -8,21 +8,38 @@
<link rel="stylesheet" href="./css/style.css">
<link rel="stylesheet" href="./css/style-desktop.css" media="screen and (min-width: 600px)">
<link rel="stylesheet" href="./css/widgets.css">
<link rel="stylesheet" href="./css/about.css">
<script type="module" src="./js/index.js"></script>
<script type="module" src="./js/components/bidi-panel.js"></script>
<script type="module" src="./js/components/upcoming-talks.js"></script>
<script type="module" src="./js/components/html.js"></script>
<script type="module" src="./js/components/talk.js"></script>
<link rel="shortcut icon" href="./assets/favicon@192.png" type="image/png">
<link rel="manifest" href="manifest.json" />
</head>
<body>
<header id="main-header">
<img src="./logo.png" alt="Interhack Camp" id="logo">
<button id="logo">
<img src="./logo.png" alt="Interhack Camp">
</button>
</header>
<section id="map"></section>
<hr>
<nav id="explore">
<details>
<summary>
<button id="explore-toggle-btn" title="Explorer les lieux"><img src="./assets/amenities-btn.svg" alt="Icone avec le signe +" /></button>
</summary>
<button title="Repos" id="explore-dodo"><img src="./icons/dortoir.svg" alt="Icone de lit"></button>
<button title="Toilettes & Douches" id="explore-toilets"><img src="./icons/toilettes.svg" alt="Icone de toilettes sur fond violet"></button>
<button title="Poubelles" id="explore-bin"><img src="./icons/poubelle.svg" alt="Icone de poubelle sur fond marron"></button>
<button title="Eau potable" id="explore-drinking-water"><img src="./icons/eau-potable.svg" alt="Icone de goutte sur fond bleu"></button>
<button title="Programmation" id="explore-on-schedule"><img src="./icons/prog.svg" alt="Icone d;orloge sur fond bleu canard"></button>
</details>
</nav>
<form id="search-section">
<label for="search-area-query">Rechercher une zone</label><br/>
@ -38,5 +55,7 @@
</button>
<camp-bidi-panel id="result-panel" ></camp-bidi-panel>
</dialog>
<template id="about-template" is="camp-html" src="./about.html"></template>
</body>
</html>

View file

@ -23,7 +23,7 @@ class CoordsElement extends HTMLElement {
}
updateContent(){
this.#shadow.replaceChildren(
this.#shadow?.replaceChildren(
this.formatCoordComponent(this.lat),
", ",
this.formatCoordComponent(this.lon)

View file

@ -13,7 +13,7 @@ TEMPLATE.innerHTML = `
height: 100%;
display: flex;
flex-direction: row;
flex-direction: column;
justify-content: center;
align-items: center;
@ -21,22 +21,23 @@ TEMPLATE.innerHTML = `
box-sizing: border-box;
border: solid 1px transparent;
gap: 3px;
}
#legend-container[hidden] {
display: none;
}
#marker-icon {
.marker-icon {
max-height: 2em;
}
#marker-icon:not([src]) {
display: none;
[data-n-marker="2"] .marker-icon {
max-height: 1.25em;
}
</style>
<div id="legend-container">
<img id="marker-icon" />
<img class="marker-icon" />
</div>
`
@ -61,11 +62,23 @@ export class FeatureLegendElement extends HTMLElement {
container.style.setProperty("--background-color", symbol.backgroundColor || "transparent");
container.style.backgroundImage = symbol.backgroundUrl ? `url(${symbol.backgroundUrl})` : "none"
container.style.borderColor = symbol.borderColor || "transparent"
if(symbol.markerUrl){
marker.src = symbol.markerUrl
} else {
marker.removeAttribute("src")
let markers = document.createDocumentFragment()
if(Array.isArray(symbol.markerUrl)){
for(let markerUrl of symbol.markerUrl){
let el = document.createElement("img")
el.classList.add("marker-icon")
el.src = markerUrl
markers.append(el)
}
} else if(symbol.markerUrl){
let el = document.createElement("img")
el.classList.add("marker-icon")
el.src = symbol.markerUrl
markers.append(el)
}
container.dataset.nMarker = markers.children.length;
container.replaceChildren(markers)
}
}

25
js/components/html.js Normal file
View file

@ -0,0 +1,25 @@
class HTMLImportElement extends HTMLTemplateElement {
get src(){
return new URL(this.getAttribute("src"), this.baseURI).toString();
}
connectedCallback(){
this.load()
}
async load(){
let res = await fetch(this.src);
if(!res.ok){
console.error(`Server responded with code ${res.status} ${res.statusText}`)
return
}
let htmlStr = await res.text()
let html = new DOMParser().parseFromString(htmlStr, "text/html");
this.content.replaceChildren(...Array.from(html.querySelector("body").childNodes))
}
}
customElements.define("camp-html", HTMLImportElement, {extends: "template"})

View file

@ -163,21 +163,27 @@ function openSearchResultItem(feature){
}
}
function openFeature(feature){
function openFeature(...feature){
let panel = document.getElementById("result-panel")
let root = document.createElement("camp-feature")
root.feature = feature
let root = document.createDocumentFragment()
for(let featureItem of feature){
let featureEl = document.createElement("camp-feature")
featureEl.feature = featureItem
root.append(featureEl)
}
panel.replaceChildren(root)
requestAnimationFrame(() => {
panel.setActiveChildrenIndex(0, {behavior: "instant"})
})
updateActiveFeature(feature)
updateActiveFeature(feature[0])
}
function updateActiveFeature(feature_or_featureid){
let newUrl = new URL(window.location)
if(feature_or_featureid){
newUrl.hash = encodeURIComponent(feature_or_featureid.id || feature_or_featureid)
if(typeof feature_or_featureid == "string"){
newUrl.hash = encodeURIComponent(feature_or_featureid)
} else if(feature_or_featureid){
newUrl.hash = encodeURIComponent(feature_or_featureid.id)
} else {
newUrl.hash = "";
}
@ -200,6 +206,33 @@ document.getElementById("result-panel").addEventListener("activePanelChange", e
}
})
document.getElementById("explore-toggle-btn").addEventListener("click", () => {
let details = document.querySelector("#explore > details")
details.open = !details.open
})
function bindButtonToCategory(button, category){
button.addEventListener("click", e => {
e.preventDefault()
openFeature(...places.featuresByCategory[category]);
})
}
bindButtonToCategory(document.getElementById("explore-drinking-water"), "drinking-water")
bindButtonToCategory(document.getElementById("explore-on-schedule"), "on-schedule")
bindButtonToCategory(document.getElementById("explore-dodo"), "dodo")
bindButtonToCategory(document.getElementById("explore-bin"), "bin")
bindButtonToCategory(document.getElementById("explore-toilets"), "toilets")
document.getElementById("logo").addEventListener("click", e => {
openFeature()
let panel = document.getElementById("result-panel")
panel.replaceChildren(document.getElementById("about-template").content.cloneNode(true));
requestAnimationFrame(() => {
panel.setActiveChildrenIndex(0, {behavior: "instant"})
})
})
{
const RESULT_PANEL = document.getElementById("result-panel")
/** @type {HTMLDialogElement} */

View file

@ -117,9 +117,12 @@ export async function init_places(places_db){
let symbol = feature.mapSymbol;
if(symbol.markerUrl) {
let iconEl = document.createElement("img")
iconEl.src = symbol.markerUrl
contentEl.append(iconEl)
let markerList = Array.isArray(symbol.markerUrl) ? symbol.markerUrl : [symbol.markerUrl];
for(let marker of markerList){
let iconEl = document.createElement("img")
iconEl.src = marker
contentEl.append(iconEl)
}
}
if(symbol.borderColor && symbol.borderColor != "white"){

View file

@ -8,6 +8,14 @@ lunrFr(lunr)
const FEATURE_INDEX = Symbol("Feature index")
export const FEATURE_ID = Symbol("Feature id")
const CATEGORIES = {
"drinking-water": (feature) => feature.properties["eau potable"] || feature.properties["douches"] || feature.properties["toilettes"],
"bin": (feature) => feature.properties["poubelle"],
"dodo": (feature) => feature.properties["n couchage"] > 0 || feature.properties["camping"],
"on-schedule": (feature) => feature.properties["pretalx-room-id"] || feature.properties["pretalx-room-id"] == 0,
"toilets": (feature) => feature.properties["toilettes"] || feature.properties["douches"]
}
/**
* Base de données locale des différents endroits du lieu
*/
@ -21,6 +29,8 @@ export class PlaceDatabase extends EventTarget {
fullTextIndex = null
featuresByCategory = {}
/**
* Résultats d'une recherche dans la base de données
* @typedef {Object} SearchResult
@ -178,6 +188,9 @@ export class PlaceDatabase extends EventTarget {
this.featuresShownOnEmptyMap = []
let showOnEmptyMap = this.featuresShownOnEmptyMap
let placesThis = this
this.featuresByCategory = {}
this.fullTextIndex = lunr(function(){
this.ref("id")
this.field("name", {boost: 3})
@ -219,6 +232,16 @@ export class PlaceDatabase extends EventTarget {
boost = sym.searchBoost
}
for(let [categoryName, categoryFilter] of Object.entries(CATEGORIES)){
if(!Array.isArray(placesThis.featuresByCategory[categoryName])){
placesThis.featuresByCategory[categoryName] = []
}
if(categoryFilter(feature)){
placesThis.featuresByCategory[categoryName].push(feature)
}
}
this.add({
id,
name: feature.properties.name || sym.genericName,

View file

@ -108,11 +108,11 @@ function getBaseSymbolForFeature(feature){
return CAMPING_SYMBOL
}
if(feature.properties["toilettes"]){
if(feature.properties["toilettes"] && feature.properties["douches"]) {
return SHOWER_AND_TOILETS_SYMBOL
} else if(feature.properties["toilettes"]){
return TOILETS_SYMBOL
}
if(feature.properties["douches"]){
} else if(feature.properties["douches"]){
return SHOWER_SYMBOL
}
@ -185,6 +185,8 @@ export const SLEEPING_SYMBOL = new MapSymbol()
{
SLEEPING_SYMBOL.markerUrl = new URL("../icons/dortoir.svg", import.meta.url)
SLEEPING_SYMBOL.genericName = "Dortoir"
SLEEPING_SYMBOL.backgroundColor = "grey"
SLEEPING_SYMBOL.borderColor = "grey"
SLEEPING_SYMBOL.indexSynonyms = [
"dodo",
"dortoir",
@ -263,6 +265,8 @@ export const TOILETS_SYMBOL = new MapSymbol()
{
TOILETS_SYMBOL.markerUrl = new URL("../icons/toilettes.svg", import.meta.url)
TOILETS_SYMBOL.genericName = "Toilettes"
TOILETS_SYMBOL.backgroundColor = "#8800ff"
TOILETS_SYMBOL.borderColor = "#8800ff"
TOILETS_SYMBOL.indexSynonyms = [
"caca",
"pipi",
@ -289,7 +293,23 @@ export const TOILETS_SYMBOL = new MapSymbol()
export const SHOWER_SYMBOL = new MapSymbol()
{
SHOWER_SYMBOL.markerUrl = new URL("../icons/douches.svg", import.meta.url)
SHOWER_SYMBOL.genericName = "Douche"
SHOWER_SYMBOL.genericName = "Douches"
SHOWER_SYMBOL.backgroundColor = "#8800ff"
SHOWER_SYMBOL.borderColor = "#8800ff"
}
export const SHOWER_AND_TOILETS_SYMBOL = new MapSymbol();
{
SHOWER_AND_TOILETS_SYMBOL.markerUrl = [
TOILETS_SYMBOL.markerUrl,
SHOWER_SYMBOL.markerUrl
]
SHOWER_AND_TOILETS_SYMBOL.backgroundColor = "#8800ff"
SHOWER_AND_TOILETS_SYMBOL.borderColor = "#8800ff"
SHOWER_AND_TOILETS_SYMBOL.genericName = "Toilettes & douches"
SHOWER_AND_TOILETS_SYMBOL.indexSynonyms = [
...TOILETS_SYMBOL.indexSynonyms
]
}
export const SWIMMING_POOL_SYMBOL = new MapSymbol()
@ -377,6 +397,8 @@ export const STAGE_SYMBOL = new MapSymbol()
{
STAGE_SYMBOL.genericName = "Scène"
STAGE_SYMBOL.markerUrl = new URL("../icons/scene.svg", import.meta.url)
STAGE_SYMBOL.backgroundColor = "#ffbf3e"
STAGE_SYMBOL.borderColor = "#ffbf3e"
STAGE_SYMBOL.indexSynonyms = [
"scène",
"musique",
@ -386,7 +408,6 @@ export const STAGE_SYMBOL = new MapSymbol()
export const MONEY_SYMBOL = new MapSymbol()
{
MONEY_SYMBOL.genericName = "Scène"
MONEY_SYMBOL.markerUrl = new URL("../icons/dons.svg", import.meta.url)
MONEY_SYMBOL.indexSynonyms = [
"argent",

View file

@ -9,6 +9,7 @@ mkdir -p "$OUT"
echo "Copie des fichiers source"
cp -v index.html "$OUT"
cp -v about.html "$OUT"
cp -v logo.png "$OUT"
cp -v manifest.json "$OUT"
cp -v -r ./js "$OUT"
@ -16,9 +17,13 @@ cp -v -r ./css "$OUT"
cp -v -r ./icons "$OUT"
mkdir "$OUT/assets"
cp -v ./assets/amenities-btn.svg "$OUT/assets"
cp -v ./assets/button-stepped-border-image.svg "$OUT/assets"
cp -v ./assets/neon-border-image.svg "$OUT/assets"
cp -v ./assets/neon-border-image-corner.svg "$OUT/assets"
cp -v ./assets/favicon.svg "$OUT/assets"
cp -v ./assets/favicon@192.png "$OUT/assets"
cp -v ./assets/favicon@512.png "$OUT/assets"
echo "Copie des données"
mkdir "$OUT/couches"