diff --git a/about.html b/about.html index 91b1f6c..300c57f 100644 --- a/about.html +++ b/about.html @@ -6,35 +6,43 @@ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> - A11y Elevator List + + + + + + hvvstuhl.de | Was ist das? -

A11y Elevator List

+

Barrierefreie Aufzugs-Liste

-
-

What is this site?

-

- This site provides an (at least more) accessible version of the HVV Website for (not) working elevators:
- HVV Website -

-

- The Source code of this page is available under: git.kritzl.dev -

-
+
+

Worum geht's hier?

+

+ Diese Seite ermöglicht einen barrierefreien Zugang zu den Aufzugs-Informationen im HVV. + Der HVV selbst zeigt diese Informationen nur auf einer (vor allem ohne Ortskenntnis) schwer zu navigierenden + Karte an: + HVV Webseite zu Aufzügen +

+

+ Dieses Projekt wurde unter der AGPL-3 Lizenz entwickelt und ist hier zu finden: + git.kritzl.dev +

+
\ No newline at end of file diff --git a/colors.css b/colors.css new file mode 100644 index 0000000..f48e716 --- /dev/null +++ b/colors.css @@ -0,0 +1,3 @@ + + +/*# sourceMappingURL=colors.css.map */ diff --git a/colors.css.map b/colors.css.map new file mode 100644 index 0000000..6ee84d1 --- /dev/null +++ b/colors.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":[],"names":[],"mappings":"","file":"colors.css"} \ No newline at end of file diff --git a/colors.scss b/colors.scss new file mode 100644 index 0000000..5c32430 --- /dev/null +++ b/colors.scss @@ -0,0 +1,271 @@ +@mixin tailwindColors { + /* Colors from Tailwind CSS: https://github.com/tailwindlabs/tailwindcss + + MIT License + + Copyright (c) Tailwind Labs, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + --color-black: #000; + --color-white: #fff; + --color-slate-50: #f8fafc; + --color-slate-100: #f1f5f9; + --color-slate-200: #e2e8f0; + --color-slate-300: #cbd5e1; + --color-slate-400: #94a3b8; + --color-slate-500: #64748b; + --color-slate-600: #475569; + --color-slate-700: #334155; + --color-slate-800: #1e293b; + --color-slate-900: #0f172a; + --color-slate-950: #020617; + --color-gray-50: #f9fafb; + --color-gray-100: #f3f4f6; + --color-gray-200: #e5e7eb; + --color-gray-300: #d1d5db; + --color-gray-400: #9ca3af; + --color-gray-500: #6b7280; + --color-gray-600: #4b5563; + --color-gray-700: #374151; + --color-gray-800: #1f2937; + --color-gray-900: #111827; + --color-gray-950: #030712; + --color-zinc-50: #fafafa; + --color-zinc-100: #f4f4f5; + --color-zinc-200: #e4e4e7; + --color-zinc-300: #d4d4d8; + --color-zinc-400: #a1a1aa; + --color-zinc-500: #71717a; + --color-zinc-600: #52525b; + --color-zinc-700: #3f3f46; + --color-zinc-800: #27272a; + --color-zinc-900: #18181b; + --color-zinc-950: #09090b; + --color-neutral-50: #fafafa; + --color-neutral-100: #f5f5f5; + --color-neutral-200: #e5e5e5; + --color-neutral-300: #d4d4d4; + --color-neutral-400: #a3a3a3; + --color-neutral-500: #737373; + --color-neutral-600: #525252; + --color-neutral-700: #404040; + --color-neutral-800: #262626; + --color-neutral-900: #171717; + --color-neutral-950: #0a0a0a; + --color-stone-50: #fafaf9; + --color-stone-100: #f5f5f4; + --color-stone-200: #e7e5e4; + --color-stone-300: #d6d3d1; + --color-stone-400: #a8a29e; + --color-stone-500: #78716c; + --color-stone-600: #57534e; + --color-stone-700: #44403c; + --color-stone-800: #292524; + --color-stone-900: #1c1917; + --color-stone-950: #0c0a09; + --color-red-50: #fef2f2; + --color-red-100: #fee2e2; + --color-red-200: #fecaca; + --color-red-300: #fca5a5; + --color-red-400: #f87171; + --color-red-500: #ef4444; + --color-red-600: #dc2626; + --color-red-700: #b91c1c; + --color-red-800: #991b1b; + --color-red-900: #7f1d1d; + --color-red-950: #450a0a; + --color-orange-50: #fff7ed; + --color-orange-100: #ffedd5; + --color-orange-200: #fed7aa; + --color-orange-300: #fdba74; + --color-orange-400: #fb923c; + --color-orange-500: #f97316; + --color-orange-600: #ea580c; + --color-orange-700: #c2410c; + --color-orange-800: #9a3412; + --color-orange-900: #7c2d12; + --color-orange-950: #431407; + --color-amber-50: #fffbeb; + --color-amber-100: #fef3c7; + --color-amber-200: #fde68a; + --color-amber-300: #fcd34d; + --color-amber-400: #fbbf24; + --color-amber-500: #f59e0b; + --color-amber-600: #d97706; + --color-amber-700: #b45309; + --color-amber-800: #92400e; + --color-amber-900: #78350f; + --color-amber-950: #451a03; + --color-yellow-50: #fefce8; + --color-yellow-100: #fef9c3; + --color-yellow-200: #fef08a; + --color-yellow-300: #fde047; + --color-yellow-400: #facc15; + --color-yellow-500: #eab308; + --color-yellow-600: #ca8a04; + --color-yellow-700: #a16207; + --color-yellow-800: #854d0e; + --color-yellow-900: #713f12; + --color-yellow-950: #422006; + --color-lime-50: #f7fee7; + --color-lime-100: #ecfccb; + --color-lime-200: #d9f99d; + --color-lime-300: #bef264; + --color-lime-400: #a3e635; + --color-lime-500: #84cc16; + --color-lime-600: #65a30d; + --color-lime-700: #4d7c0f; + --color-lime-800: #3f6212; + --color-lime-900: #365314; + --color-lime-950: #1a2e05; + --color-green-50: #f0fdf4; + --color-green-100: #dcfce7; + --color-green-200: #bbf7d0; + --color-green-300: #86efac; + --color-green-400: #4ade80; + --color-green-500: #22c55e; + --color-green-600: #16a34a; + --color-green-700: #15803d; + --color-green-800: #166534; + --color-green-900: #14532d; + --color-green-950: #052e16; + --color-emerald-50: #ecfdf5; + --color-emerald-100: #d1fae5; + --color-emerald-200: #a7f3d0; + --color-emerald-300: #6ee7b7; + --color-emerald-400: #34d399; + --color-emerald-500: #10b981; + --color-emerald-600: #059669; + --color-emerald-700: #047857; + --color-emerald-800: #065f46; + --color-emerald-900: #064e3b; + --color-emerald-950: #022c22; + --color-teal-50: #f0fdfa; + --color-teal-100: #ccfbf1; + --color-teal-200: #99f6e4; + --color-teal-300: #5eead4; + --color-teal-400: #2dd4bf; + --color-teal-500: #14b8a6; + --color-teal-600: #0d9488; + --color-teal-700: #0f766e; + --color-teal-800: #115e59; + --color-teal-900: #134e4a; + --color-teal-950: #042f2e; + --color-cyan-50: #ecfeff; + --color-cyan-100: #cffafe; + --color-cyan-200: #a5f3fc; + --color-cyan-300: #67e8f9; + --color-cyan-400: #22d3ee; + --color-cyan-500: #06b6d4; + --color-cyan-600: #0891b2; + --color-cyan-700: #0e7490; + --color-cyan-800: #155e75; + --color-cyan-900: #164e63; + --color-cyan-950: #083344; + --color-sky-50: #f0f9ff; + --color-sky-100: #e0f2fe; + --color-sky-200: #bae6fd; + --color-sky-300: #7dd3fc; + --color-sky-400: #38bdf8; + --color-sky-500: #0ea5e9; + --color-sky-600: #0284c7; + --color-sky-700: #0369a1; + --color-sky-800: #075985; + --color-sky-900: #0c4a6e; + --color-sky-950: #082f49; + --color-blue-50: #eff6ff; + --color-blue-100: #dbeafe; + --color-blue-200: #bfdbfe; + --color-blue-300: #93c5fd; + --color-blue-400: #60a5fa; + --color-blue-500: #3b82f6; + --color-blue-600: #2563eb; + --color-blue-700: #1d4ed8; + --color-blue-800: #1e40af; + --color-blue-900: #1e3a8a; + --color-blue-950: #172554; + --color-indigo-50: #eef2ff; + --color-indigo-100: #e0e7ff; + --color-indigo-200: #c7d2fe; + --color-indigo-300: #a5b4fc; + --color-indigo-400: #818cf8; + --color-indigo-500: #6366f1; + --color-indigo-600: #4f46e5; + --color-indigo-700: #4338ca; + --color-indigo-800: #3730a3; + --color-indigo-900: #312e81; + --color-indigo-950: #1e1b4b; + --color-violet-50: #f5f3ff; + --color-violet-100: #ede9fe; + --color-violet-200: #ddd6fe; + --color-violet-300: #c4b5fd; + --color-violet-400: #a78bfa; + --color-violet-500: #8b5cf6; + --color-violet-600: #7c3aed; + --color-violet-700: #6d28d9; + --color-violet-800: #5b21b6; + --color-violet-900: #4c1d95; + --color-violet-950: #2e1065; + --color-purple-50: #faf5ff; + --color-purple-100: #f3e8ff; + --color-purple-200: #e9d5ff; + --color-purple-300: #d8b4fe; + --color-purple-400: #c084fc; + --color-purple-500: #a855f7; + --color-purple-600: #9333ea; + --color-purple-700: #7e22ce; + --color-purple-800: #6b21a8; + --color-purple-900: #581c87; + --color-purple-950: #3b0764; + --color-fuchsia-50: #fdf4ff; + --color-fuchsia-100: #fae8ff; + --color-fuchsia-200: #f5d0fe; + --color-fuchsia-300: #f0abfc; + --color-fuchsia-400: #e879f9; + --color-fuchsia-500: #d946ef; + --color-fuchsia-600: #c026d3; + --color-fuchsia-700: #a21caf; + --color-fuchsia-800: #86198f; + --color-fuchsia-900: #701a75; + --color-fuchsia-950: #4a044e; + --color-pink-50: #fdf2f8; + --color-pink-100: #fce7f3; + --color-pink-200: #fbcfe8; + --color-pink-300: #f9a8d4; + --color-pink-400: #f472b6; + --color-pink-500: #ec4899; + --color-pink-600: #db2777; + --color-pink-700: #be185d; + --color-pink-800: #9d174d; + --color-pink-900: #831843; + --color-pink-950: #500724; + --color-rose-50: #fff1f2; + --color-rose-100: #ffe4e6; + --color-rose-200: #fecdd3; + --color-rose-300: #fda4af; + --color-rose-400: #fb7185; + --color-rose-500: #f43f5e; + --color-rose-600: #e11d48; + --color-rose-700: #be123c; + --color-rose-800: #9f1239; + --color-rose-900: #881337; + --color-rose-950: #4c0519; + /* End: Colors from Tailwind CSS */ +} \ No newline at end of file diff --git a/elevators.js b/elevators.js index 34b9713..4908cfb 100644 --- a/elevators.js +++ b/elevators.js @@ -1,206 +1,120 @@ -Object.defineProperty(String.prototype, 'capitalize', { - value: function () { - return this.charAt(0).toUpperCase() + this.toLowerCase().slice(1); +const internalData = { + lastUpdate: 0, + stations: [], +}; +let geolocationPermission = false; +let geolocation = null; +const openStations = new Set(); +let sortByDistance = false; + +const substituteData = [ + { + name: 'Borgweg (Stadtpark)', + coordinates: [53.5907696, 10.0147719], }, - enumerable: false -}); + { + name: 'Emilienstraße', + coordinates: [53.5716862, 9.9525424], + }, + { + name: 'Garstedt', + coordinates: [53.6844739, 9.9860415], + }, + { + name: 'Hagenbecks Tierpark', + coordinates: [53.5925874, 9.9440359], + }, + { + name: 'Hamburg Hbf', + searchTarget: "Hauptbahnhof", + }, + { + name: 'Jungfernstieg', + searchTarget: "Rathaus", + }, + { + name: 'Rathaus', + searchTarget: "Jungfernstieg", + }, + { + name: 'Stephansplatz (Oper/CCH)', + searchTarget: "Dammtor (Messe/CCH)", + }, + { + name: 'Dammtor (Messe/CCH)', + searchTarget: "Stephansplatz (Oper/CCH)", + }, + { + name: 'Hauptbahnhof Nord', + coordinates: [53.5541197, 10.0061270], + }, + { + name: 'Hoheneichen', + coordinates: [53.6355141, 10.0677176], + }, + { + name: 'Kornweg (Klein Borstel)', + coordinates: [53.6324430, 10.0541722], + }, + { + name: 'Lauenbrück', + coordinates: [53.1971209, 9.5640765], + }, + { + name: 'Lutterothstraße', + coordinates: [53.5819938, 9.9476215], + }, + { + name: 'Meckelfeld', + coordinates: [53.4248897, 10.0291223], + }, + { + name: 'Sengelmannstraße (City Nord)', + coordinates: [53.6093953, 10.0220004], + }, + { + name: 'St.Pauli', + coordinates: [53.5507957, 9.9700752], + }, + { + name: 'Winsen(Luhe)', + coordinates: [53.3534304, 10.2086841], + }, +] async function loadElevators() { - const res = await fetch('https://www.hvv.de/elevators', {referrer: ""}); + document.querySelector('#errorMessage').classList.add('hidden'); + const res = await fetch('https://www.hvv.de/elevators', { + referrer: "", + }).catch(e => { + document.querySelector('#errorMessage').classList.remove('hidden'); + }); const data = await res.json(); - localStorage.setItem("elevator_data", JSON.stringify(data)); -} -const dateTimeStyle = new Intl.DateTimeFormat('de-DE', { - dateStyle: 'medium', - timeStyle: 'medium', - timeZone: 'Europe/Berlin', -}) - -const internalData = [] -let geolocationPermission = false; -let geolocation = [null, null]; - -async function loadOsmData() { - const elevatorNodes = document.querySelectorAll('.elevator'); - const nodeIdList = []; - for (const elevator of elevatorNodes) { - const osmContainer = elevator.querySelector('.osm'); - const nodeId = osmContainer.dataset.nodeid; - if (nodeId > 0) { - elevator.querySelector('.osm').insertAdjacentHTML("beforeend", '
loading...
'); - nodeIdList.push(nodeId) - } - } - - const osmData = await fetch(`https://overpass-api.de/api/interpreter?data=[out:json];node(id:${nodeIdList.join(',')});out%20body;`, {}); - const osmJson = await osmData.json(); - - if (!osmJson.hasOwnProperty('elements')) return; - - for (const elevator of elevatorNodes) { - const tagContainer = elevator.querySelector('.osmTags'); - const nodeId = elevator.querySelector('.osm').dataset.nodeid; - - const nodes = osmJson.elements.filter(node => node.id === parseInt(nodeId)) - if (!nodes.length) { - tagContainer.innerHTML = 'keine Daten'; - continue; - } - const nodeInfo = nodes[0]; - - if (!nodeInfo.hasOwnProperty('tags')) { - tagContainer.innerHTML = 'keine Daten'; - continue; - } - const tags = nodeInfo['tags']; - - if (tags['highway'] === 'elevator') { - let osmTemplate = ''; - osmTemplate += `
Link zur Karte
-Auf Karte anzeigen -
`; - if (tags.hasOwnProperty('description')) { - osmTemplate += `
Beschreibung
${tags['description']}
`; - } - if (tags.hasOwnProperty('level')) { - osmTemplate += `
Ebenen
${tags['level'].split(';').sort().join(', ')}
`; - } - if (tags.hasOwnProperty('wheelchair')) { - osmTemplate += `
Rollstühle
${tags['wheelchair'] === 'yes' ? 'Ja' : 'Nein'}
`; - } - if (tags.hasOwnProperty('bicycle')) { - osmTemplate += `
Fahrräder
${tags['bicycle'] === 'yes' ? 'Ja' : 'Nein'}
`; - } - tagContainer.innerHTML = ''; // clear loading state - tagContainer.insertAdjacentHTML('beforeend', osmTemplate); - } - } -} - -function distance([lat1, lon1], [lat2, lon2]) { - if (!lat1 || !lat2 || !lon1 || !lon2) return null - const R = 6371e3; // metres - const phi1 = lat1 * Math.PI / 180; // φ, λ in radians - const phi2 = lat2 * Math.PI / 180; - const dPhi = (lat2 - lat1) * Math.PI / 180; - const dLambda = (lon2 - lon1) * Math.PI / 180; - - const a = Math.sin(dPhi / 2) * Math.sin(dPhi / 2) + - Math.cos(phi1) * Math.cos(phi2) * - Math.sin(dLambda / 2) * Math.sin(dLambda / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - return R * c; // in metres -} - -function registerGeolocationWatcher() { - navigator.geolocation.watchPosition((pos) => { - geolocation = [pos.coords.latitude, pos.coords.longitude] - }, (e) => { - console.log(e) - }, { - enableHighAccuracy: true, - timeout: 5000, - maximumAge: 0, - }) -} - -function checkGeolocationPermission() { - navigator.permissions.query({name: "geolocation"}).then((result) => { - geolocationPermission = result.state - if (result.state === 'granted') { - registerGeolocationWatcher() - } - }); -} - -function allowGeolocation() { - navigator.geolocation.getCurrentPosition(() => { - console.log('success') - }, () => { - console.log('error') - }); - - checkGeolocationPermission() - registerGeolocationWatcher() -} - -checkGeolocationPermission() - -document.querySelector('#stationsNearMe').addEventListener('click', allowGeolocation) - -function sortStations(stationA, stationB) { - const nameA = stationA.mainSubStation.stationName.toUpperCase(); // ignore upper and lowercase - const nameB = stationB.mainSubStation.stationName.toUpperCase(); // ignore upper and lowercase - if (nameA < nameB) { - return -1; - } - if (nameA > nameB) { - return 1; - } - - // names must be equal - return 0; -} - -function getType(line) { - const type = line.replace(/[^A-z]/g, ""); - switch (type) { - case 'RE': - case 'RB': - case 'R': - case 'DB': - return 'R'; - case 'S': - return 'S'; - case 'U': - return 'U'; - case 'A': - return 'A'; - } -} - -function getTypes(lines) { - const types = new Set(); - for (const line of lines) { - types.add(getType(line)) - } - return types; -} - -function renderData() { - const data = JSON.parse(localStorage.getItem("elevator_data")); - - if (!data) { - console.error('No Data available!') - return; - } - - let stations = data.stations; - stations.sort(sortStations) - const date = new Date(data.lastUpdate); - - document.querySelector('#loadElevators').innerHTML = 'Daten aktualisieren'; - const listContainer = document.querySelector('#stationList'); - const dateContainer = document.querySelector('#lastUpdated'); - - dateContainer.innerHTML = dateTimeStyle.format(date); + const stations = data['stations']; + stations.sort(sortStations); + internalData.lastUpdate = new Date(data['lastUpdate']); let stationIndex = 0; - //clear list before update - listContainer.innerHTML = ''; for (const station of stations) { - const stationName = station.mainSubStation.stationName; + const stationName = station['mainSubStation']['stationName']; + const stationComment = station['mainSubStation']['comment']; + let searchTarget = undefined; + + const substitute = substituteData.filter(subs => subs.name === stationName) + + if (substitute.length && substitute[0].hasOwnProperty('searchTarget')) { + searchTarget = substitute[0].searchTarget; + } const lines = new Set(); const elevators = []; for (const elevatorKey of Object.keys(station.elevators)) { const elevatorApi = station.elevators[elevatorKey]; const elevatorLines = []; for (let line of elevatorApi.lines) { - line = line.replace(/[\(\)]/g, ""); + line = line.replace(/[()]/g, ""); lines.add(line); elevatorLines.push({ line: line, @@ -240,19 +154,320 @@ function renderData() { elevators.push(elevator); } - const stationTypes = getTypes(Array.from(lines)); - - let elevatorsTemplate = ''; - let previewTemplate = ''; let stationState = { unavailable: 0, working: 0, outOfOrder: 0, } + for (const elevator of elevators) { - let linesTemplate = '
Linien: '; - for (const line of elevator.lines) { - linesTemplate += `${line.line}`; + if (elevator.stateUnavailable) { + stationState.unavailable++; + } else if (elevator.working) { + stationState.working++; + } else { + stationState.outOfOrder++; + } + } + + const stationLines = Array.from(lines); + const stationTypes = Array.from(getTypes(stationLines)); + + internalData.stations[stationIndex++] = { + name: stationName, + comment: stationComment, + searchTarget: searchTarget, + state: stationState, + lines: stationLines, + types: stationTypes, + elevators: elevators, + } + } + + localStorage.setItem("internal_data", JSON.stringify({ + api: minorVersion, + ...internalData + })); +} + +async function loadOsmData() { + const nodeIdList = []; + for (const station of internalData.stations) { + for (const elevator of station.elevators) { + nodeIdList.push(elevator.osmNodeId) + } + } + + const osmResponse = await fetch(`https://overpass-api.de/api/interpreter?data=[out:json];node(id:${nodeIdList.join(',')});out%20body;`, {}); + const osmJson = await osmResponse.json(); + + if (!osmJson.hasOwnProperty('elements')) return; + + const osmNodes = {}; + for await (const node of osmJson.elements) { + if (node.hasOwnProperty('tags')) { + const tags = node['tags']; + if (tags['highway'] === 'elevator') { + osmNodes[node['id']] = node; + } else { + console.warn(`OSM Node is not an elevator. (NodeID: ${node['id']})`); + } + } else { + console.warn(`OSM Node has no Tags. (NodeID: ${node['id']})`); + } + } + + //update coordinates in stations + for (const stationIndex in internalData.stations) { + const station = internalData.stations[stationIndex]; + for (const elevator of station.elevators) { + const node = osmNodes[elevator.osmNodeId] + if (node) { + internalData.stations[stationIndex]['coordinates'] = [ + node['lat'], + node['lon'], + ] + } + } + + if (!internalData.stations[stationIndex].hasOwnProperty('coordinates')) { + const substitute = substituteData.filter(subs => subs.name === internalData.stations[stationIndex].name) + console.log(substitute) + if (substitute.length && substitute[0].hasOwnProperty('coordinates')) { + internalData.stations[stationIndex]['coordinates'] = substitute[0].coordinates; + } + } + } + + localStorage.setItem("osm_data", JSON.stringify({ + api: minorVersion, + lastUpdate: new Date(), + nodes: osmNodes + })); + localStorage.setItem("internal_data", JSON.stringify({ + api: minorVersion, + ...internalData + })); +} + +function distance([lat1, lon1], [lat2, lon2]) { + if (!lat1 || !lat2 || !lon1 || !lon2) return null + const R = 6371e3; // metres + const phi1 = lat1 * Math.PI / 180; // φ, λ in radians + const phi2 = lat2 * Math.PI / 180; + const dPhi = (lat2 - lat1) * Math.PI / 180; + const dLambda = (lon2 - lon1) * Math.PI / 180; + + const a = Math.sin(dPhi / 2) * Math.sin(dPhi / 2) + + Math.cos(phi1) * Math.cos(phi2) * + Math.sin(dLambda / 2) * Math.sin(dLambda / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return R * c; // in metres +} + +function registerGeolocationWatcher() { + navigator.geolocation.watchPosition((pos) => { + if (geolocation === null) { + geolocation = [pos.coords.latitude, pos.coords.longitude]; + renderData(geolocation); + } + }, (e) => { + console.warn(e) + }, { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0, + }) +} + +function checkGeolocationPermission() { + navigator.permissions.query({name: "geolocation"}).then((result) => { + geolocationPermission = result.state + if (result.state === 'granted') { + registerGeolocationWatcher() + } + }); +} + +function allowGeolocation() { + navigator.geolocation.getCurrentPosition(() => { + console.log('success') + }, () => { + console.log('error') + }); + + checkGeolocationPermission() + registerGeolocationWatcher() +} + +checkGeolocationPermission() + +function sortStations(stationA, stationB) { + const nameA = stationA.mainSubStation.stationName.toUpperCase(); // ignore upper and lowercase + const nameB = stationB.mainSubStation.stationName.toUpperCase(); // ignore upper and lowercase + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + + // names must be equal + return 0; +} + +function sortInt(valueA, valueB) { + const a = parseInt(valueA) + const b = parseInt(valueB) + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + + // names must be equal + return 0; +} + +function sortStationsByDistance(stationA, stationB) { + const distanceA = stationA.distance ?? 0; // ignore upper and lowercase + const distanceB = stationB.distance ?? 0; // ignore upper and lowercase + if (distanceA < distanceB) { + return -1; + } + if (distanceA > distanceB) { + return 1; + } + + // names must be equal + return 0; +} + +function getType(line) { + const type = line.replace(/[^A-z]/g, ""); + switch (type) { + case 'RE': + case 'RB': + case 'R': + case 'DB': + return 'R'; + case 'S': + return 'S'; + case 'U': + return 'U'; + case 'A': + return 'A'; + } +} + +function getTypes(lines) { + const types = new Set(); + for (const line of lines) { + types.add(getType(line)) + } + return types; +} + +function renderData(location = null) { + const ls = JSON.parse(localStorage.getItem("internal_data")); + if (!ls) return; + internalData.lastUpdate = ls['lastUpdate']; + internalData.stations = ls['stations']; + const osmData = JSON.parse(localStorage.getItem("osm_data")); + + if (!internalData || !internalData.stations.length) { + console.error('No Data available!') + return; + } + + if ( + location !== null + && ( + location.length !== 2 + || typeof location[0] !== 'number' + || typeof location[1] !== 'number' + ) + ) { + console.error('No valid location provided') + return; + } + + document.querySelector('#updateInfo').classList.remove('hidden'); + document.querySelector('#loadElevators').classList.remove('hidden'); + document.querySelector('#filters').classList.remove('hidden'); + document.querySelector('#initialLoad').classList.add('hidden'); + const dateContainer = document.querySelector('#lastUpdated'); + const oldDataWarning = document.querySelector('#oldDataWarning'); + + const lastUpdate = new Date(internalData.lastUpdate); + const now = new Date(); + dateContainer.innerHTML = dateTimeStyle.format(lastUpdate); + oldDataWarning.classList.add('hidden'); + if (now - lastUpdate > 86400 * 1000) { + const days = numberFormat.format((now - lastUpdate) / (86400 * 1000)); + oldDataWarning.classList.remove('hidden'); + oldDataWarning.innerHTML = `Daten ${days} Tag${days !== '1' ? 'e' : ''} alt!`; + } + + const listContainer = document.querySelector('#stationList'); + //clear list before update + listContainer.innerHTML = ''; + + let stations = [...internalData['stations']]; + + for (const stationIndex in stations) { + const station = stations[stationIndex]; + station.id = stationIndex; + + if (location !== null) { + if (station.hasOwnProperty('coordinates')) { + station.distance = distance(location, station.coordinates); + } else { + console.log('station has no position:', station.name); + } + } + } + + if (sortByDistance) { + stations = stations.sort(sortStationsByDistance); + } + + for (const stationIndex in stations) { + const station = stations[stationIndex]; + + if (location !== null) { + if (station.hasOwnProperty('coordinates')) { + station.distance = distance(location, station.coordinates); + } else { + console.log('station has no position:', station.name); + } + } + + let elevatorsTemplate = ''; + let previewTemplate = ''; + + for (const elevator of station.elevators) { + const stateTemplate = ` + `; + + let linesTemplate = ''; + linesTemplate = `
${stateTemplate}`; + if (elevator.lines.length) { + linesTemplate = `
${stateTemplate}`; + linesTemplate += `
Linien: `; + for (const line of elevator.lines) { + linesTemplate += `${line.line}`; + } + linesTemplate += '
'; } linesTemplate += '
'; @@ -262,28 +477,49 @@ function renderData() { } levelsTemplate += ''; + let osmTemplate = ''; + if (osmData) { + const node = osmData.nodes[elevator.osmNodeId] + if (node) { + + osmTemplate = '
'; + osmTemplate += `
+
Link zur Karte
+
+ + Auf Karte anzeigen + +
+
`; + if (node.tags.hasOwnProperty('description')) { + osmTemplate += `
Beschreibung
${node.tags['description']}
`; + } + if (node.tags.hasOwnProperty('level')) { + osmTemplate += `
Ebenen
${node.tags['level'].split(';').sort(sortInt).join(', ')}
`; + } + if (node.tags.hasOwnProperty('wheelchair')) { + osmTemplate += `
Rollstühle
${node.tags['wheelchair'] === 'yes' ? 'Ja' : 'Nein'}
`; + } + if (node.tags.hasOwnProperty('bicycle')) { + osmTemplate += `
Fahrräder
${node.tags['bicycle'] === 'yes' ? 'Ja' : 'Nein'}
`; + } + + osmTemplate += '
'; + } else { + console.warn(`OSM Node not found (deleted). At:\t${station.name}\t${elevator.label} (NodeID: ${elevator.osmNodeId})`); + } + } else { + console.warn(`Elevator has no OSM Node id:\t${station.name}\t${elevator}`); + } + + previewTemplate += `` - if (elevator.stateUnavailable) { - stationState.unavailable++; - } else if (elevator.working) { - stationState.working++; - } else { - stationState.outOfOrder++; - } - elevatorsTemplate += `
  • - - + ${stateTemplate}
    + ${linesTemplate} ${elevator.instCause !== '' ? `
    ${elevator.instCause}
    ` : ''} ${elevator.levels.length ? levelsTemplate : elevator.description}
    @@ -308,79 +544,202 @@ function renderData() {
    ${elevator.speaker === -1 ? 'unbekannt' : elevator.speaker ? `verfügbar` : 'nicht verfügbar'}
    - ${elevator.lines.length ? `${linesTemplate}` : ''} -
    -

    Daten von OpenStreetMap

    -
    + + ${osmTemplate ? `
    +
    +

    Daten von OpenStreetMap

    + +
    + ${osmTemplate} +
    ` : ``}
  • `; } - const template = `
  • + const template = `
  • +
    - ${Array.from(stationTypes).sort().map(t => `${t}`).join('')} + ${station.types.sort().map(t => `${t}`).join('')} +
    + ${sortByDistance + ? typeof station.distance !== 'undefined' + ? `
    ${Math.round(station.distance / 100) / 10}
    km
    ` + : '
    ? km
    ' + : ''}
    -

    ${stationName}

    +

    ${station.name}

    -
    +
    - Aufzüge anzeigen + Details / Aufzüge anzeigen -
  • `; listContainer.insertAdjacentHTML('beforeend', template); - // immediate invocation - // (function () { - // const stationId = stationIndex; - // listContainer.querySelector(`#station_${stationIndex}`) - // .addEventListener('click', () => toggleElevatorList(stationId)) - // }()); + //immediate invocation + (function () { + listContainer.querySelectorAll(`#station_${station.id} .loadOSM`).forEach(e => { + e.addEventListener('click', (ev) => { + ev.target.querySelector('.spinner').classList.remove('hidden'); + loadOsmData().then(() => { + ev.target.classList.add('hidden'); + renderData(); + }); + }) + }) + }()); - - internalData[stationIndex++] = { - name: stationName, - state: stationState, - elevators: elevators, - } } + + listContainer.insertAdjacentHTML('beforeend', `
  • +0 von 0 Stationen durch Filter ausgeblendet. +
  • `); + + listContainer.querySelectorAll(`details`).forEach(e => { + e.addEventListener("toggle", (event) => { + const stationId = event.target.dataset['stationid']; + if (event.target.open) { + openStations.add(stationId); + } else { + openStations.delete(stationId); + } + }); + }) + + filterData(); } document.querySelector('#loadElevators') .addEventListener('click', (e) => { - loadElevators().then(() => renderData()); + e.target.querySelector('.spinner').classList.remove('hidden'); + loadElevators().then(() => { + e.target.querySelector('.spinner').classList.add('hidden'); + renderData(); + }); }) -document.querySelector('#loadOSM') +document.querySelector('#initialLoad') .addEventListener('click', (e) => { - loadOsmData(); + e.target.querySelector('.spinner').classList.remove('hidden'); + loadElevators().then(() => { + e.target.classList.add('hidden'); + renderData(); + }); }) -renderData(); +document.querySelector('#loadOsm') + .addEventListener('click', (e) => { + e.target.querySelector('.spinner').classList.remove('hidden'); + loadOsmData().then(() => { + e.target.querySelector('.spinner').classList.add('hidden'); + renderData(); + closeDialog('#dialog_osm'); + }); + }) + +document.querySelector('#stationsNearMe') + .addEventListener('click', async (e) => { + e.target.querySelector('.spinner').classList.remove('hidden'); + if (!sortByDistance) { + if (JSON.parse(localStorage.getItem("osm_data")) === null) { + openDialog('#dialog_osm'); + } else { + if (geolocationPermission !== 'granted') { + allowGeolocation(); + } else { + sortByDistance = e.target.ariaPressed = true; + + // If geolocation is already set. + // If not the location watcher will re-render our data. + if (geolocation !== null) { + renderData(geolocation) + } + } + } + } else { + sortByDistance = e.target.ariaPressed = false; + + renderData(); + } + e.target.querySelector('.spinner').classList.add('hidden'); + }) -function filterData(searchString) { - for (const stationIndex in internalData) { - const matches = internalData[stationIndex].name.toLowerCase().search(searchString.toLowerCase()) >= 0; - document.querySelector(`#station_${stationIndex}`).classList.toggle('hidden', !matches); +function filterData() { + const searchString = document.querySelector('#searchStation').value; + const typeU = document.querySelector('button.typeChip[data-type="U"]'); + const typeS = document.querySelector('button.typeChip[data-type="S"]'); + const typeA = document.querySelector('button.typeChip[data-type="A"]'); + const typeR = document.querySelector('button.typeChip[data-type="R"]'); + const activeTypes = []; + if (typeU.dataset.pressed === 'true') activeTypes.push('U'); + if (typeS.dataset.pressed === 'true') activeTypes.push('S'); + if (typeA.dataset.pressed === 'true') activeTypes.push('A'); + if (typeR.dataset.pressed === 'true') activeTypes.push('R'); + const stationCount = internalData.stations.length; + let filteredStations = 0; + if (internalData) { + for (const stationIndex in internalData.stations) { + const matchesName = internalData.stations[stationIndex].name.toLowerCase().search(searchString.toLowerCase()) >= 0; + const matchesSearchTarget = internalData.stations[stationIndex].hasOwnProperty('searchTarget') + ? internalData.stations[stationIndex].searchTarget.toLowerCase().search(searchString.toLowerCase()) >= 0 + : false; + let matchesType = false; + internalData.stations[stationIndex].types.forEach(type => { + if (activeTypes.includes(type)) matchesType = true; + }) + const filtered = !((matchesName || matchesSearchTarget) && matchesType); + document.querySelector(`#station_${stationIndex}`).classList.toggle('hidden', filtered); + if (filtered) filteredStations++; + } + document.querySelector('#stationCount').innerHTML = stationCount; + document.querySelector('#filteredCount').innerHTML = filteredStations; } } document.querySelector('#searchStation').addEventListener('input', (e) => { - filterData(e.target.value); + filterData(); }) -filterData(document.querySelector('#searchStation').value) \ No newline at end of file +document.querySelectorAll('button.typeChip').forEach(e => { + e.addEventListener('click', (event) => { + e.ariaPressed = e.dataset.pressed = e.dataset.pressed === 'true' ? 'false' : 'true'; + filterData(); + }) +}) + +// data api version check +const check_internal = JSON.parse(localStorage.getItem("internal_data")); +const check_osm = JSON.parse(localStorage.getItem("osm_data")); +if (check_internal === null || check_internal.hasOwnProperty('api') && check_internal.api === minorVersion) { + if (check_osm === null || check_osm.hasOwnProperty('api') && check_osm.api === minorVersion) { + renderData(); + } else { + console.log('osm_data: version mismatch') + localStorage.removeItem('osm_data'); + } +} else { + console.log('internal_data: version mismatch') + localStorage.removeItem('internal_data'); +} diff --git a/icons/192.png b/icons/192.png new file mode 100644 index 0000000..a329713 Binary files /dev/null and b/icons/192.png differ diff --git a/icons/512-maskable.png b/icons/512-maskable.png new file mode 100644 index 0000000..6122f35 Binary files /dev/null and b/icons/512-maskable.png differ diff --git a/icons/512-transparent.png b/icons/512-transparent.png new file mode 100644 index 0000000..c6eed8c Binary files /dev/null and b/icons/512-transparent.png differ diff --git a/icons/512.png b/icons/512.png new file mode 100644 index 0000000..79acdc4 Binary files /dev/null and b/icons/512.png differ diff --git a/icons/favicon-maskable.svg b/icons/favicon-maskable.svg new file mode 100644 index 0000000..cb8cda2 --- /dev/null +++ b/icons/favicon-maskable.svg @@ -0,0 +1,58 @@ + + + + diff --git a/icons/favicon-transparent.svg b/icons/favicon-transparent.svg new file mode 100644 index 0000000..dcea345 --- /dev/null +++ b/icons/favicon-transparent.svg @@ -0,0 +1,55 @@ + + + + diff --git a/icons/favicon.ico b/icons/favicon.ico new file mode 100644 index 0000000..29e9f5e Binary files /dev/null and b/icons/favicon.ico differ diff --git a/icons/favicon.svg b/icons/favicon.svg new file mode 100644 index 0000000..4222b18 --- /dev/null +++ b/icons/favicon.svg @@ -0,0 +1,58 @@ + + + + diff --git a/icons/favicon_source.svg b/icons/favicon_source.svg new file mode 100644 index 0000000..028ef75 --- /dev/null +++ b/icons/favicon_source.svg @@ -0,0 +1,85 @@ + + + + diff --git a/images/screenshot-desktop.png b/images/screenshot-desktop.png new file mode 100644 index 0000000..4856c04 Binary files /dev/null and b/images/screenshot-desktop.png differ diff --git a/images/screenshot-mobile.png b/images/screenshot-mobile.png new file mode 100644 index 0000000..2856686 Binary files /dev/null and b/images/screenshot-mobile.png differ diff --git a/index.html b/index.html index 183742c..2099374 100644 --- a/index.html +++ b/index.html @@ -6,44 +6,96 @@ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> - A11y Elevator List + + + + + + hvvstuhl.de -

    A11y Elevator List

    +

    Barrierefreie Aufzugs-Liste

    -

    Station List

    -
    - Last Updated: - - -
    -
    - -
    - -
    + + + + + + +
    + + +
    + +
    + \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..dff3f0b --- /dev/null +++ b/main.js @@ -0,0 +1,51 @@ +const version = '0.6.4' +const minorVersion = version.split('.').splice(0, 2).join('.'); +const numberFormat = new Intl.NumberFormat('de-DE', { + maximumFractionDigits: 1 +}); + +Object.defineProperty(String.prototype, 'capitalize', { + value: function () { + return this.charAt(0).toUpperCase() + this.toLowerCase().slice(1); + }, + enumerable: false +}); + +const dateTimeStyle = new Intl.DateTimeFormat('de-DE', { + dateStyle: 'medium', + timeStyle: 'medium', + timeZone: 'Europe/Berlin', +}) + +// set version +document.querySelector('#version').innerHTML = `v${version}`; + + +function openDialog(selector) { + document.querySelector('body').classList.add('has-dialog') + document.querySelector('#dialog_layer').classList.add('active') + document.querySelector(selector).classList.remove('hidden') +} + +function closeDialog(selector) { + document.querySelector('body').classList.remove('has-dialog') + document.querySelector('#dialog_layer').classList.remove('active') + document.querySelector(selector).classList.add('hidden') +} + +if ("serviceWorker" in navigator) { + navigator.serviceWorker.register("/sw.js") + .then((registration) => { + if (registration.installing) { + console.log('New Service Worker is installing...'); + } else if (registration.waiting) { + console.log('Installed new service worker. Waiting for Update (up to 24h).'); + } else if (registration.active) { + console.log('Service Worker is up to date.'); + } + }) + .catch((error) => { + // registration failed + console.error(`Registration failed with ${error}`); + }); +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..662cd51 --- /dev/null +++ b/manifest.json @@ -0,0 +1,49 @@ +{ + "name": "hvvstuhl.de", + "short_name": "hvvstuhl", + "start_url": ".", + "display": "standalone", + "background_color": "#030712", + "theme_color": "#65a30d", + "description": "Barrierefreie Aufzugs-Liste", + "icons": [ + { + "src": "/icons/192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "/icons/512-maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/icons/512-transparent.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "monochrome" + } + ], + "screenshots" : [ + { + "src": "images/screenshot-mobile.png", + "sizes": "430x900", + "type": "image/png", + "form_factor": "narrow", + "label": "Stationsliste" + }, + { + "src": "images/screenshot-desktop.png", + "sizes": "1280x720", + "type": "image/png", + "form_factor": "wide", + "label": "Stationsliste" + } + ] +} diff --git a/md_icons/close.svg b/md_icons/close.svg new file mode 100644 index 0000000..555e083 --- /dev/null +++ b/md_icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/md_icons/load.svg b/md_icons/load.svg new file mode 100644 index 0000000..3a1bc93 --- /dev/null +++ b/md_icons/load.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/style.css b/style.css index 25e49c9..331675e 100644 --- a/style.css +++ b/style.css @@ -1,31 +1,307 @@ :root { - --color-gray-50: hsl(60, 9%, 98%); - --color-gray-100: hsl(60, 5%, 96%); - --color-gray-200: hsl(20, 6%, 90%); - --color-gray-300: hsl(24, 6%, 83%); - --color-gray-400: hsl(24, 5%, 64%); - --color-gray-500: hsl(25, 5%, 45%); - --color-gray-600: hsl(33, 5%, 32%); - --color-gray-700: hsl(30, 6%, 25%); - --color-gray-800: hsl(12, 6%, 15%); - --color-gray-900: hsl(24, 10%, 10%); - --color-gray-950: hsl(20, 14%, 4%); + /* Colors from Tailwind CSS: https://github.com/tailwindlabs/tailwindcss + + MIT License + + Copyright (c) Tailwind Labs, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + --color-black: #000; + --color-white: #fff; + --color-slate-50: #f8fafc; + --color-slate-100: #f1f5f9; + --color-slate-200: #e2e8f0; + --color-slate-300: #cbd5e1; + --color-slate-400: #94a3b8; + --color-slate-500: #64748b; + --color-slate-600: #475569; + --color-slate-700: #334155; + --color-slate-800: #1e293b; + --color-slate-900: #0f172a; + --color-slate-950: #020617; + --color-gray-50: #f9fafb; + --color-gray-100: #f3f4f6; + --color-gray-200: #e5e7eb; + --color-gray-300: #d1d5db; + --color-gray-400: #9ca3af; + --color-gray-500: #6b7280; + --color-gray-600: #4b5563; + --color-gray-700: #374151; + --color-gray-800: #1f2937; + --color-gray-900: #111827; + --color-gray-950: #030712; + --color-zinc-50: #fafafa; + --color-zinc-100: #f4f4f5; + --color-zinc-200: #e4e4e7; + --color-zinc-300: #d4d4d8; + --color-zinc-400: #a1a1aa; + --color-zinc-500: #71717a; + --color-zinc-600: #52525b; + --color-zinc-700: #3f3f46; + --color-zinc-800: #27272a; + --color-zinc-900: #18181b; + --color-zinc-950: #09090b; + --color-neutral-50: #fafafa; + --color-neutral-100: #f5f5f5; + --color-neutral-200: #e5e5e5; + --color-neutral-300: #d4d4d4; + --color-neutral-400: #a3a3a3; + --color-neutral-500: #737373; + --color-neutral-600: #525252; + --color-neutral-700: #404040; + --color-neutral-800: #262626; + --color-neutral-900: #171717; + --color-neutral-950: #0a0a0a; + --color-stone-50: #fafaf9; + --color-stone-100: #f5f5f4; + --color-stone-200: #e7e5e4; + --color-stone-300: #d6d3d1; + --color-stone-400: #a8a29e; + --color-stone-500: #78716c; + --color-stone-600: #57534e; + --color-stone-700: #44403c; + --color-stone-800: #292524; + --color-stone-900: #1c1917; + --color-stone-950: #0c0a09; + --color-red-50: #fef2f2; + --color-red-100: #fee2e2; + --color-red-200: #fecaca; + --color-red-300: #fca5a5; + --color-red-400: #f87171; + --color-red-500: #ef4444; + --color-red-600: #dc2626; + --color-red-700: #b91c1c; + --color-red-800: #991b1b; + --color-red-900: #7f1d1d; + --color-red-950: #450a0a; + --color-orange-50: #fff7ed; + --color-orange-100: #ffedd5; + --color-orange-200: #fed7aa; + --color-orange-300: #fdba74; + --color-orange-400: #fb923c; + --color-orange-500: #f97316; + --color-orange-600: #ea580c; + --color-orange-700: #c2410c; + --color-orange-800: #9a3412; + --color-orange-900: #7c2d12; + --color-orange-950: #431407; + --color-amber-50: #fffbeb; + --color-amber-100: #fef3c7; + --color-amber-200: #fde68a; + --color-amber-300: #fcd34d; + --color-amber-400: #fbbf24; + --color-amber-500: #f59e0b; + --color-amber-600: #d97706; + --color-amber-700: #b45309; + --color-amber-800: #92400e; + --color-amber-900: #78350f; + --color-amber-950: #451a03; + --color-yellow-50: #fefce8; + --color-yellow-100: #fef9c3; + --color-yellow-200: #fef08a; + --color-yellow-300: #fde047; + --color-yellow-400: #facc15; + --color-yellow-500: #eab308; + --color-yellow-600: #ca8a04; + --color-yellow-700: #a16207; + --color-yellow-800: #854d0e; + --color-yellow-900: #713f12; + --color-yellow-950: #422006; + --color-lime-50: #f7fee7; + --color-lime-100: #ecfccb; + --color-lime-200: #d9f99d; + --color-lime-300: #bef264; + --color-lime-400: #a3e635; + --color-lime-500: #84cc16; + --color-lime-600: #65a30d; + --color-lime-700: #4d7c0f; + --color-lime-800: #3f6212; + --color-lime-900: #365314; + --color-lime-950: #1a2e05; + --color-green-50: #f0fdf4; + --color-green-100: #dcfce7; + --color-green-200: #bbf7d0; + --color-green-300: #86efac; + --color-green-400: #4ade80; + --color-green-500: #22c55e; + --color-green-600: #16a34a; + --color-green-700: #15803d; + --color-green-800: #166534; + --color-green-900: #14532d; + --color-green-950: #052e16; + --color-emerald-50: #ecfdf5; + --color-emerald-100: #d1fae5; + --color-emerald-200: #a7f3d0; + --color-emerald-300: #6ee7b7; + --color-emerald-400: #34d399; + --color-emerald-500: #10b981; + --color-emerald-600: #059669; + --color-emerald-700: #047857; + --color-emerald-800: #065f46; + --color-emerald-900: #064e3b; + --color-emerald-950: #022c22; + --color-teal-50: #f0fdfa; + --color-teal-100: #ccfbf1; + --color-teal-200: #99f6e4; + --color-teal-300: #5eead4; + --color-teal-400: #2dd4bf; + --color-teal-500: #14b8a6; + --color-teal-600: #0d9488; + --color-teal-700: #0f766e; + --color-teal-800: #115e59; + --color-teal-900: #134e4a; + --color-teal-950: #042f2e; + --color-cyan-50: #ecfeff; + --color-cyan-100: #cffafe; + --color-cyan-200: #a5f3fc; + --color-cyan-300: #67e8f9; + --color-cyan-400: #22d3ee; + --color-cyan-500: #06b6d4; + --color-cyan-600: #0891b2; + --color-cyan-700: #0e7490; + --color-cyan-800: #155e75; + --color-cyan-900: #164e63; + --color-cyan-950: #083344; + --color-sky-50: #f0f9ff; + --color-sky-100: #e0f2fe; + --color-sky-200: #bae6fd; + --color-sky-300: #7dd3fc; + --color-sky-400: #38bdf8; + --color-sky-500: #0ea5e9; + --color-sky-600: #0284c7; + --color-sky-700: #0369a1; + --color-sky-800: #075985; + --color-sky-900: #0c4a6e; + --color-sky-950: #082f49; + --color-blue-50: #eff6ff; + --color-blue-100: #dbeafe; + --color-blue-200: #bfdbfe; + --color-blue-300: #93c5fd; + --color-blue-400: #60a5fa; + --color-blue-500: #3b82f6; + --color-blue-600: #2563eb; + --color-blue-700: #1d4ed8; + --color-blue-800: #1e40af; + --color-blue-900: #1e3a8a; + --color-blue-950: #172554; + --color-indigo-50: #eef2ff; + --color-indigo-100: #e0e7ff; + --color-indigo-200: #c7d2fe; + --color-indigo-300: #a5b4fc; + --color-indigo-400: #818cf8; + --color-indigo-500: #6366f1; + --color-indigo-600: #4f46e5; + --color-indigo-700: #4338ca; + --color-indigo-800: #3730a3; + --color-indigo-900: #312e81; + --color-indigo-950: #1e1b4b; + --color-violet-50: #f5f3ff; + --color-violet-100: #ede9fe; + --color-violet-200: #ddd6fe; + --color-violet-300: #c4b5fd; + --color-violet-400: #a78bfa; + --color-violet-500: #8b5cf6; + --color-violet-600: #7c3aed; + --color-violet-700: #6d28d9; + --color-violet-800: #5b21b6; + --color-violet-900: #4c1d95; + --color-violet-950: #2e1065; + --color-purple-50: #faf5ff; + --color-purple-100: #f3e8ff; + --color-purple-200: #e9d5ff; + --color-purple-300: #d8b4fe; + --color-purple-400: #c084fc; + --color-purple-500: #a855f7; + --color-purple-600: #9333ea; + --color-purple-700: #7e22ce; + --color-purple-800: #6b21a8; + --color-purple-900: #581c87; + --color-purple-950: #3b0764; + --color-fuchsia-50: #fdf4ff; + --color-fuchsia-100: #fae8ff; + --color-fuchsia-200: #f5d0fe; + --color-fuchsia-300: #f0abfc; + --color-fuchsia-400: #e879f9; + --color-fuchsia-500: #d946ef; + --color-fuchsia-600: #c026d3; + --color-fuchsia-700: #a21caf; + --color-fuchsia-800: #86198f; + --color-fuchsia-900: #701a75; + --color-fuchsia-950: #4a044e; + --color-pink-50: #fdf2f8; + --color-pink-100: #fce7f3; + --color-pink-200: #fbcfe8; + --color-pink-300: #f9a8d4; + --color-pink-400: #f472b6; + --color-pink-500: #ec4899; + --color-pink-600: #db2777; + --color-pink-700: #be185d; + --color-pink-800: #9d174d; + --color-pink-900: #831843; + --color-pink-950: #500724; + --color-rose-50: #fff1f2; + --color-rose-100: #ffe4e6; + --color-rose-200: #fecdd3; + --color-rose-300: #fda4af; + --color-rose-400: #fb7185; + --color-rose-500: #f43f5e; + --color-rose-600: #e11d48; + --color-rose-700: #be123c; + --color-rose-800: #9f1239; + --color-rose-900: #881337; + --color-rose-950: #4c0519; + /* End: Colors from Tailwind CSS */ + --bg-type-u: #006ab3; + --bg-type-s: #1a962b; + --bg-type-a: #f39100; + --bg-type-r: #000000; + --bg-type-u-disabled: #c3cfd7; + --bg-type-s-disabled: #c6d4c8; + --bg-type-a-disabled: #dfd8cf; + --bg-type-r-disabled: #b3b3b3; + --color-type-disabled: var(--color-gray-400); + --color-line-U1: #006ab3; + --color-line-U2: #e2001a; + --color-line-U3: #ffdd00; + --color-line-U4: #0098a1; + --color-line-S1: #1a962b; + --color-line-S2: #b51143; + --color-line-S3: #622181; + --color-line-S4: #be148e; + --color-line-S5: #0089bb; + --color-line-S6: #d3da00; --bg: var(--color-gray-50); --text: var(--color-gray-950); --text-secondary: var(--color-gray-700); - --nav-bg: hsl(210, 60%, 50%); - --nav-bg-hover: hsl(210, 60%, 30%); - --nav-bg-visited: hsl(250, 60%, 50%); - --nav-text-visited: hsl(250, 60%, 93%); - --nav-text: hsl(210, 60%, 93%); - --link: hsl(210, 60%, 30%); - --link-hover: hsl(210, 60%, 10%); - --link-visited: hsl(250, 60%, 30%); + --nav-bg: var(--color-indigo-400); + --nav-bg-hover: var(--color-indigo-200); + --nav-bg-visited: var(--color-purple-400); + --link: var(--color-indigo-600); + --link-hover: var(--color-indigo-800); + --link-visited: var(--color-purple-600); --station-bg: var(--color-gray-50); --station-bg-hover: var(--color-gray-400); --station-border: var(--color-gray-400); --station-border-hover: var(--color-gray-600); --elevator-bg: var(--color-gray-100); + --backdrop: rgb(0 0 0 / 20%); --elevator-list-gap: 3rem; --item-radius: 1.5rem; --btn-radius: 0.8rem; @@ -34,45 +310,101 @@ --space-m: 0.5rem; --space-l: 1rem; --space-xl: 2rem; + --space-2xl: 3rem; } @media (prefers-color-scheme: dark) { :root { + --bg-type-u-disabled: #627f93; + --bg-type-s-disabled: #688b6d; + --bg-type-a-disabled: #aa987f; + --bg-type-r-disabled: #333333; + --color-type-disabled: var(--color-gray-200); --bg: var(--color-gray-950); --text: var(--color-gray-50); --text-secondary: var(--color-gray-400); - --nav-bg: hsl(210, 60%, 30%); - --nav-bg-hover: hsl(210, 60%, 10%); - --nav-bg-visited: hsl(250, 60%, 30%); - --link: hsl(210, 60%, 60%); - --link-hover: hsl(210, 60%, 60%); - --link-visited: hsl(250, 60%, 70%); + --nav-bg: var(--color-indigo-800); + --nav-bg-hover: var(--color-indigo-950); + --nav-bg-visited: var(--color-purple-800); + --link: var(--color-indigo-400); + --link-hover: var(--color-indigo-200); + --link-visited: var(--color-purple-400); --station-bg: var(--color-gray-950); --station-bg-hover: var(--color-gray-600); --station-border: var(--color-gray-600); --station-border-hover: var(--color-gray-400); --elevator-bg: var(--color-gray-900); + --backdrop: rgb(100% 100% 100% / 20%); } } -.hidden { +body.has-dialog { + overflow: hidden; +} + +.dialogs { display: none; + position: fixed; + overflow-y: auto; + max-width: 100vw; + padding: 1em; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 99; + background: var(--backdrop); +} +.dialogs.active { + display: block; +} +.dialogs [role=dialog] { + box-sizing: border-box; + max-width: 800px; + margin: 0 auto; + padding: var(--space-xl); + padding-bottom: var(--space-2xl); + background-color: var(--bg); + color: var(--text); + overflow: hidden; + position: absolute; + right: 0; + bottom: 0; + left: 0; + border-radius: var(--item-radius) var(--item-radius) 0 0; +} +@media screen and (min-width: 576px) { + .dialogs [role=dialog] { + position: relative; + border-radius: var(--item-radius); + } +} +.dialogs [role=dialog] button.close-modal { + position: absolute; + top: 0; + right: 0; + border-radius: 0 0 0 var(--btn-radius); + border: none; +} + +.hidden { + display: none !important; } .text-red { - color: #e32d2d; + color: var(--color-red-800); } .text-orange { - color: #cc6300; + color: var(--color-orange-500); } .text-green { - color: #19942e; + color: var(--color-green-600); } body { font-family: system-ui, ui-sans-serif, sans-serif; - font-size: 1.2rem; + font-size: 1rem; background-color: var(--bg); color: var(--text); } @@ -85,7 +417,7 @@ a { color: var(--link); text-decoration: none; } -a:hover { +a:hover, a:visited:hover { text-decoration: underline; color: var(--link-hover); } @@ -103,18 +435,18 @@ nav ul { } nav ul li a { font-weight: bold; - color: var(--nav-text); + color: var(--text); text-decoration: none; background-color: var(--nav-bg); padding: var(--space-m) var(--space-l); cursor: pointer; } -nav ul li a:hover { +nav ul li a:hover, nav ul li a:visited:hover { text-decoration: underline; background-color: var(--nav-bg-hover); } nav ul li a:visited { - color: var(--nav-text-visited); + color: var(--text); background-color: var(--nav-bg-visited); } @@ -123,6 +455,13 @@ main { max-width: 960px; } +footer { + display: flex; + justify-content: center; + margin: 1em auto; + max-width: 960px; +} + button, input { background-color: var(--station-bg); color: var(--text); @@ -141,13 +480,124 @@ button:hover { background-color: var(--station-bg-hover); border-color: var(--station-border-hover); } - -div.filters { - display: flex; - gap: var(--space-m); - justify-items: stretch; +button[aria-pressed=true] { + background-color: var(--nav-bg-visited); } -div.filters button, div.filters input { +button.size-s { + font-size: 0.8rem; + padding: var(--space-s) var(--space-m); +} +button.size-m { + padding: var(--space-m) var(--space-l); +} +button.size-l { + font-size: 1.5rem; + padding: var(--space-m) var(--space-l); +} + +div#updateInfo { + display: flex; + align-items: center; + gap: var(--space-m); + justify-content: end; + flex-direction: column; + margin-bottom: var(--space-l); +} +@media screen and (min-width: 576px) { + div#updateInfo { + flex-direction: row; + } +} +div#updateInfo #oldDataWarning { + color: var(--color-red-600); + font-weight: bold; +} + +div#filters { + display: flex; + flex-wrap: wrap; + gap: var(--space-m); + justify-content: center; +} +div#filters > * { + width: 100%; + justify-content: center; + text-align: center; +} +@media screen and (min-width: 576px) { + div#filters > * { + width: auto; + min-width: 0; + } +} +div#filters input#searchStation { + flex-grow: 1; + font-size: 1.5rem; + min-width: 10ch; +} +div#filters button#stationsNearMe { + font-size: 1.5rem; +} +div#filters div#typeFilter { + font-size: 1.5rem; + display: flex; + justify-content: center; + flex-shrink: 0; + min-height: var(--space-2xl); + gap: var(--space-m); +} +div#filters div#typeFilter button.typeChip { + flex-shrink: 0; + padding: 0; + height: var(--space-2xl); + aspect-ratio: 1/1; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; + border-radius: 9999px; +} +div#filters div#typeFilter button.typeChip[data-type=U], div#filters div#typeFilter button.typeChip[data-type=S], div#filters div#typeFilter button.typeChip[data-type=A], div#filters div#typeFilter button.typeChip[data-type=R] { + border: solid 2px var(--text); +} +div#filters div#typeFilter button.typeChip[data-type=U][data-pressed=false], div#filters div#typeFilter button.typeChip[data-type=S][data-pressed=false], div#filters div#typeFilter button.typeChip[data-type=A][data-pressed=false], div#filters div#typeFilter button.typeChip[data-type=R][data-pressed=false] { + background-color: var(--color-gray-500); + color: var(--color-type-disabled); +} +div#filters div#typeFilter button.typeChip[data-type=U][data-pressed=false][data-type=U], div#filters div#typeFilter button.typeChip[data-type=S][data-pressed=false][data-type=U], div#filters div#typeFilter button.typeChip[data-type=A][data-pressed=false][data-type=U], div#filters div#typeFilter button.typeChip[data-type=R][data-pressed=false][data-type=U] { + background-color: var(--bg-type-u-disabled); +} +div#filters div#typeFilter button.typeChip[data-type=U][data-pressed=false][data-type=S], div#filters div#typeFilter button.typeChip[data-type=S][data-pressed=false][data-type=S], div#filters div#typeFilter button.typeChip[data-type=A][data-pressed=false][data-type=S], div#filters div#typeFilter button.typeChip[data-type=R][data-pressed=false][data-type=S] { + background-color: var(--bg-type-s-disabled); +} +div#filters div#typeFilter button.typeChip[data-type=U][data-pressed=false][data-type=A], div#filters div#typeFilter button.typeChip[data-type=S][data-pressed=false][data-type=A], div#filters div#typeFilter button.typeChip[data-type=A][data-pressed=false][data-type=A], div#filters div#typeFilter button.typeChip[data-type=R][data-pressed=false][data-type=A] { + background-color: var(--bg-type-a-disabled); +} +div#filters div#typeFilter button.typeChip[data-type=U][data-pressed=false][data-type=R], div#filters div#typeFilter button.typeChip[data-type=S][data-pressed=false][data-type=R], div#filters div#typeFilter button.typeChip[data-type=A][data-pressed=false][data-type=R], div#filters div#typeFilter button.typeChip[data-type=R][data-pressed=false][data-type=R] { + background-color: var(--bg-type-r-disabled); +} +div#filters div#typeFilter button.typeChip[data-type=U][data-pressed=false]::after, div#filters div#typeFilter button.typeChip[data-type=S][data-pressed=false]::after, div#filters div#typeFilter button.typeChip[data-type=A][data-pressed=false]::after, div#filters div#typeFilter button.typeChip[data-type=R][data-pressed=false]::after { + position: absolute; + content: ""; + width: 144%; + height: 0.4rem; + background-color: var(--color-gray-700); + transform: rotate(-45deg); +} + +button#initialLoad { + font-size: 1.5rem; +} + +button.loadOSM { + width: fit-content; +} + +div#errorMessage { + border: solid 2px var(--color-red-600); + border-radius: var(--item-radius); + padding: var(--space-xl) var(--space-xl); + margin-top: var(--space-l); font-size: 1.5rem; } @@ -158,6 +608,11 @@ ul#stationList { flex-direction: column; gap: var(--space-m); } +ul#stationList li#filterInfo { + text-align: center; + padding: var(--space-l) var(--space-xl); + font-style: italic; +} ul#stationList > li.station { background-color: var(--station-bg); border-radius: var(--item-radius); @@ -170,9 +625,20 @@ ul#stationList > li.station > div.stationSummary { display: flex; gap: var(--space-m); } -ul#stationList > li.station > div.stationSummary div.typeList { +ul#stationList > li.station > div.stationSummary div.symbol { + position: relative; +} +ul#stationList > li.station > div.stationSummary div.symbol div.typeList { flex-shrink: 0; } +ul#stationList > li.station > div.stationSummary div.symbol div.distance { + position: absolute; + width: 100%; + bottom: -2lh; + text-align: center; + color: var(--text-secondary); + font-size: 0.9em; +} ul#stationList > li.station > div.stationSummary div.stationTitle { align-self: stretch; flex-grow: 1; @@ -207,17 +673,23 @@ ul#stationList > li.station > div.stationSummary div.stationTitle > div.elevator } ul#stationList > li.station > details { width: 100%; + display: flex; + flex-direction: column; } ul#stationList > li.station > details summary { + width: 100%; list-style: none; + display: inline-flex; + cursor: pointer; + margin-left: calc(var(--space-m) + var(--type-icon-size)); +} +ul#stationList > li.station > details summary > span { border: solid 2px var(--station-border); display: inline-flex; padding: var(--space-m) var(--space-l); border-radius: var(--btn-radius); - cursor: pointer; - margin-left: calc(var(--space-m) + var(--type-icon-size)); } -ul#stationList > li.station > details summary:hover { +ul#stationList > li.station > details summary:hover > span { background-color: var(--station-bg-hover); border-color: var(--station-border-hover); } @@ -240,6 +712,20 @@ ul#stationList > li.station > details summary::after { ul#stationList > li.station > details[open] summary { margin-bottom: 1rem; } +ul#stationList > li.station > details > div.comment { + position: relative; + z-index: 20; + display: inline-flex; + margin-left: calc(var(--space-m) + var(--type-icon-size)); + padding-bottom: var(--space-m); + border-bottom: solid 2px var(--station-border); +} +ul#stationList > li.station > details > div.comment > p { + display: block; + border: solid 2px var(--nav-bg); + border-radius: var(--btn-radius); + padding: var(--space-l); +} ul#stationList > li.station > details > ul { padding: 0; list-style: none; @@ -254,18 +740,29 @@ ul#stationList > li.station > details > ul > li.elevator { display: flex; align-items: start; flex-wrap: nowrap; - padding: var(--space-m); gap: var(--space-m); position: relative; - margin: 0 1rem 1rem; + margin: 0 var(--space-s) var(--space-s); + padding: var(--space-s); } @media screen and (min-width: 576px) { ul#stationList > li.station > details > ul > li.elevator { - margin: 0 3rem 1rem; + margin: 0 var(--space-2xl) 1rem; + padding: var(--space-m); + padding-right: var(--space-2xl); } } -ul#stationList > li.station > details > ul > li.elevator > span { +ul#stationList > li.station > details > ul > li.elevator > .stateIcon { flex-shrink: 0; + display: none; +} +@media screen and (min-width: 576px) { + ul#stationList > li.station > details > ul > li.elevator > .stateIcon { + display: inline; + } +} +ul#stationList > li.station > details > ul > li.elevator:first-child { + padding-top: var(--space-l); } ul#stationList > li.station > details > ul > li.elevator:not(:first-child):before { content: ""; @@ -293,15 +790,50 @@ ul#stationList > li.station > details > ul > li.elevator > div.elevatorData .ele font-weight: bold; grid-column: 1/-1; } -ul#stationList > li.station > details > ul > li.elevator > div.elevatorData .lineList { +ul#stationList > li.station > details > ul > li.elevator > div.elevatorData div.firstRow { + grid-column: 1/-1; + display: flex; + gap: var(--space-m); +} +@media screen and (min-width: 576px) { + ul#stationList > li.station > details > ul > li.elevator > div.elevatorData div.firstRow.hiddenOnDesktop { + display: none; + } +} +@media screen and (min-width: 576px) { + ul#stationList > li.station > details > ul > li.elevator > div.elevatorData div.firstRow .stateIcon { + display: none; + } +} +ul#stationList > li.station > details > ul > li.elevator > div.elevatorData div.firstRow .lineList { display: flex; gap: var(--space-m); align-items: center; + min-height: var(--space-2xl); +} +ul#stationList > li.station > details > ul > li.elevator > div.elevatorData hr { grid-column: 1/-1; + width: 100%; + margin: 0; + border: none; + border-bottom: dashed 2px var(--station-border); } ul#stationList > li.station > details > ul > li.elevator > div.elevatorData .osm { grid-column: 1/-1; color: var(--text-secondary); + border: dashed 2px var(--station-border); + border-radius: var(--item-radius); + padding: var(--space-l); +} +ul#stationList > li.station > details > ul > li.elevator > div.elevatorData .osm .osmHeading { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--space-l); + margin-bottom: var(--space-l); +} +ul#stationList > li.station > details > ul > li.elevator > div.elevatorData .osm .osmHeading h4 { + margin: 0; } ul#stationList > li.station > details > ul > li.elevator > div.elevatorData .osm dl div dt { min-width: 18ch; @@ -361,14 +893,15 @@ ul#stationList > li.station > details > ul > li.elevator > div.elevatorData dl d font-weight: bold; min-width: 15ch; } -@media (max-width: 576px) { +@media (max-width: 400px) { ul#stationList > li.station > details > ul > li.elevator > div.elevatorData dl div dt { width: 100%; } } ul#stationList > li.station > details > ul > li.elevator > div.elevatorData dl div dd { - flex-shrink: 0; - margin-left: 3rem; + width: 0; + flex-grow: 1; + margin-left: var(--space-2xl); } @media screen and (min-width: 576px) { ul#stationList > li.station > details > ul > li.elevator > div.elevatorData dl div dd { @@ -376,63 +909,66 @@ ul#stationList > li.station > details > ul > li.elevator > div.elevatorData dl d } } -span.lineChip, span.typeChip { +span.lineChip, span.typeChip, button.typeChip { font-weight: bold; font-size: 0.8em; text-align: center; box-sizing: border-box; } -span.lineChip[data-type=U], span.typeChip[data-type=U] { - background-color: #006ab3; - color: #ffffff; +span.lineChip[data-type=U], span.typeChip[data-type=U], button.typeChip[data-type=U] { + background-color: var(--bg-type-u); + color: var(--color-white); } -span.lineChip[data-type=U][data-line=U1], span.typeChip[data-type=U][data-line=U1] { - background-color: #006ab3; +span.lineChip[data-type=U][data-line=U1], span.typeChip[data-type=U][data-line=U1], button.typeChip[data-type=U][data-line=U1] { + background-color: var(--color-line-U1); } -span.lineChip[data-type=U][data-line=U2], span.typeChip[data-type=U][data-line=U2] { - background-color: #e2001a; +span.lineChip[data-type=U][data-line=U2], span.typeChip[data-type=U][data-line=U2], button.typeChip[data-type=U][data-line=U2] { + background-color: var(--color-line-U2); } -span.lineChip[data-type=U][data-line=U3], span.typeChip[data-type=U][data-line=U3] { - background-color: #ffdd00; - color: #000000; +span.lineChip[data-type=U][data-line=U3], span.typeChip[data-type=U][data-line=U3], button.typeChip[data-type=U][data-line=U3] { + background-color: var(--color-line-U3); + color: var(--color-black); } -span.lineChip[data-type=U][data-line=U4], span.typeChip[data-type=U][data-line=U4] { - background-color: #0098a1; +span.lineChip[data-type=U][data-line=U4], span.typeChip[data-type=U][data-line=U4], button.typeChip[data-type=U][data-line=U4] { + background-color: var(--color-line-U4); } -span.lineChip[data-type=S], span.typeChip[data-type=S] { - background-color: #1a962b; - color: #ffffff; +span.lineChip[data-type=U][data-line=U5], span.typeChip[data-type=U][data-line=U5], button.typeChip[data-type=U][data-line=U5] { + background-color: var(--color-line-U5); } -span.lineChip[data-type=S][data-line=S1], span.typeChip[data-type=S][data-line=S1] { - background-color: #1a962b; +span.lineChip[data-type=S], span.typeChip[data-type=S], button.typeChip[data-type=S] { + background-color: var(--bg-type-s); + color: var(--color-white); } -span.lineChip[data-type=S][data-line=S2], span.typeChip[data-type=S][data-line=S2] { - background-color: #b51143; +span.lineChip[data-type=S][data-line=S1], span.typeChip[data-type=S][data-line=S1], button.typeChip[data-type=S][data-line=S1] { + background-color: var(--color-line-S1); } -span.lineChip[data-type=S][data-line=S3], span.typeChip[data-type=S][data-line=S3] { - background-color: #622181; +span.lineChip[data-type=S][data-line=S2], span.typeChip[data-type=S][data-line=S2], button.typeChip[data-type=S][data-line=S2] { + background-color: var(--color-line-S2); } -span.lineChip[data-type=S][data-line=S4], span.typeChip[data-type=S][data-line=S4] { - background-color: #be148e; +span.lineChip[data-type=S][data-line=S3], span.typeChip[data-type=S][data-line=S3], button.typeChip[data-type=S][data-line=S3] { + background-color: var(--color-line-S3); } -span.lineChip[data-type=S][data-line=S5], span.typeChip[data-type=S][data-line=S5] { - background-color: #0089bb; +span.lineChip[data-type=S][data-line=S4], span.typeChip[data-type=S][data-line=S4], button.typeChip[data-type=S][data-line=S4] { + background-color: var(--color-line-S4); } -span.lineChip[data-type=S][data-line=S6], span.typeChip[data-type=S][data-line=S6] { - background-color: #d3da00; - color: #000000; +span.lineChip[data-type=S][data-line=S5], span.typeChip[data-type=S][data-line=S5], button.typeChip[data-type=S][data-line=S5] { + background-color: var(--color-line-S5); } -span.lineChip[data-type=A], span.typeChip[data-type=A] { - background-color: #f39100; - color: #ffffff; +span.lineChip[data-type=S][data-line=S6], span.typeChip[data-type=S][data-line=S6], button.typeChip[data-type=S][data-line=S6] { + background-color: var(--color-line-S6); + color: var(--color-black); } -span.lineChip[data-type=R], span.typeChip[data-type=R] { - background-color: #000000; - color: #ffffff; +span.lineChip[data-type=A], span.typeChip[data-type=A], button.typeChip[data-type=A] { + background-color: var(--bg-type-a); + color: var(--color-white); +} +span.lineChip[data-type=R], span.typeChip[data-type=R], button.typeChip[data-type=R] { + background-color: var(--bg-type-r); + color: var(--color-white); } @media (prefers-color-scheme: dark) { - span.lineChip[data-type=R], span.typeChip[data-type=R] { - border: solid 2px #ffffff; + span.lineChip[data-type=R], span.typeChip[data-type=R], button.typeChip[data-type=R] { + border: solid 2px var(--color-white); } } @@ -542,5 +1078,27 @@ span[data-icon][data-icon=up-down] { span[data-icon][data-icon=location-searching] { mask-image: url(/md_icons/location-searching.svg); } +span[data-icon][data-icon=load] { + mask-image: url(/md_icons/load.svg); +} +span[data-icon][data-icon=close] { + mask-image: url(/md_icons/close.svg); +} +span[data-icon].spinner { + animation-delay: 0s; + animation-direction: normal; + animation-duration: 1s; + animation-fill-mode: none; + animation-iteration-count: infinite; + animation-name: spinner; + animation-play-state: running; + animation-timing-function: linear; +} + +@keyframes spinner { + to { + transform: rotate(360deg); + } +} /*# sourceMappingURL=style.css.map */ diff --git a/style.css.map b/style.css.map index 108bf0f..56af578 100644 --- a/style.css.map +++ b/style.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAmBA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAxBA;EA/BF;IAgCI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;;AAaJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EAGA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGE;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAOV;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAIF;EACE;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAEA;;AAEA;EAPF;IAQI;;;AAIJ;EACE;EAEA;EACA;EACA;EACA;;AAEA;EARF;IASI;IACA;IACA;;;AAMR;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EA1BF;IA2BI;;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAEA;;AACA;EATF;IAUI;;;AAIF;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EAEA;;AACA;EARF;IASI;;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAII;EACE;;AAMR;EACE;EACA;EACA;EAEA;EACA;;AAEA;EACE;EAEA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;;AAKF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;;;AAIJ;EACE;EACA;;AAEA;EAJF;IAKI;;;;AAYpB;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI;;;;AAKN;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EAEE;EAMA;EACA;EACA;EACA;EACA;EACA;EACA;;AAVA;EACE;;AAWF;EAIE;EACA;;;AAMJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"style.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["style.scss","colors.scss"],"names":[],"mappings":"AAMA;ACLE;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAwBA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACA;ED/PA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAIA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EAgCA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAtCA;EApDF;IAsDI;IACA;IACA;IACA;IACA;IAGA;IACA;IACA;IAEA;IACA;IACA;IAEA;IACA;IACA;IAEA;IACA;IACA;IACA;IACA;IAEA;;;;AAeF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAfF;IAgBI;IACA;;;AAIA;EACE;EACA;EACA;EACA;EACA;;;AAMR;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EAGA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGE;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAOV;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AACA;EAJF;IAKI;IACA;;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAQZ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAKJ;EACE;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAEA;;AAEA;EAPF;IAQI;;;AAIJ;EACE;EAEA;EACA;EACA;EACA;;AAEA;EARF;IASI;IACA;IACA;;;AAMR;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAjCF;IAkCI;;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EAEA;EACA;;AACA;EATF;IAUI;IACA;IACA;;;AAIF;EACE;EACA;;AACA;EAHF;IAII;;;AAKJ;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EAEA;;AACA;EARF;IASI;;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGE;EADF;IAEI;;;AAKF;EADF;IAEI;;;AAKJ;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAOA;EACE;;AAMR;EACE;EACA;EACA;EAEA;EACA;;AAEA;EACE;EAEA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;;AAKF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;;;AAIJ;EACE;EACA;EAEA;;AAEA;EANF;IAOI;;;;AAYpB;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI;;;;AAMN;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EAEE;EAMA;EACA;EACA;EACA;EACA;EACA;EACA;;AAVA;EACE;;AAWF;EAIE;EACA;;;AAMJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;IACE","file":"style.css"} \ No newline at end of file diff --git a/style.scss b/style.scss index a731775..49a4971 100644 --- a/style.scss +++ b/style.scss @@ -1,52 +1,89 @@ +@import "colors"; + $mobileBreakpoint: 576px; +$smallBreakpoint: 400px; $mediumBreakpoint: 800px; :root { - --color-gray-50: hsl(60, 9%, 98%); - --color-gray-100: hsl(60, 5%, 96%); - --color-gray-200: hsl(20, 6%, 90%); - --color-gray-300: hsl(24, 6%, 83%); - --color-gray-400: hsl(24, 5%, 64%); - --color-gray-500: hsl(25, 5%, 45%); - --color-gray-600: hsl(33, 5%, 32%); - --color-gray-700: hsl(30, 6%, 25%); - --color-gray-800: hsl(12, 6%, 15%); - --color-gray-900: hsl(24, 10%, 10%); - --color-gray-950: hsl(20, 14%, 4%); + @include tailwindColors(); + + // LineType Colors + $colorTypeU: #006ab3; + $colorTypeS: #1a962b; + $colorTypeA: #f39100; + $colorTypeR: #000000; + --bg-type-u: #{$colorTypeU}; + --bg-type-s: #{$colorTypeS}; + --bg-type-a: #{$colorTypeA}; + --bg-type-r: #{$colorTypeR}; + --bg-type-u-disabled: #{scale-color($colorTypeU, $saturation: -80%, $lightness: +70%)}; + --bg-type-s-disabled: #{scale-color($colorTypeS, $saturation: -80%, $lightness: +70%)}; + --bg-type-a-disabled: #{scale-color($colorTypeA, $saturation: -80%, $lightness: +70%)}; + --bg-type-r-disabled: #{scale-color($colorTypeR, $saturation: -80%, $lightness: +70%)}; + --color-type-disabled: var(--color-gray-400); + + // Line Colors + --color-line-U1: #006ab3; + --color-line-U2: #e2001a; + --color-line-U3: #ffdd00; + --color-line-U4: #0098a1; + --color-line-S1: #1a962b; + --color-line-S2: #b51143; + --color-line-S3: #622181; + --color-line-S4: #be148e; + --color-line-S5: #0089bb; + --color-line-S6: #d3da00; + // Component Styles --bg: var(--color-gray-50); --text: var(--color-gray-950); --text-secondary: var(--color-gray-700); - --nav-bg: hsl(210, 60%, 50%); - --nav-bg-hover: hsl(210, 60%, 30%); - --nav-bg-visited: hsl(250, 60%, 50%); - --nav-text-visited: hsl(250, 60%, 93%); - --nav-text: hsl(210, 60%, 93%); - --link: hsl(210, 60%, 30%); - --link-hover: hsl(210, 60%, 10%); - --link-visited: hsl(250, 60%, 30%); + + --nav-bg: var(--color-indigo-400); + --nav-bg-hover: var(--color-indigo-200); + --nav-bg-visited: var(--color-purple-400); + + --link: var(--color-indigo-600); + --link-hover: var(--color-indigo-800); + --link-visited: var(--color-purple-600); + --station-bg: var(--color-gray-50); --station-bg-hover: var(--color-gray-400); --station-border: var(--color-gray-400); --station-border-hover: var(--color-gray-600); --elevator-bg: var(--color-gray-100); + --backdrop: rgb(0 0 0 / 20%); + @media (prefers-color-scheme: dark) { + // LineType Colors + --bg-type-u-disabled: #{scale-color($colorTypeU, $saturation: -80%, $lightness: +20%)}; + --bg-type-s-disabled: #{scale-color($colorTypeS, $saturation: -80%, $lightness: +20%)}; + --bg-type-a-disabled: #{scale-color($colorTypeA, $saturation: -80%, $lightness: +20%)}; + --bg-type-r-disabled: #{scale-color($colorTypeR, $saturation: -80%, $lightness: +20%)}; + --color-type-disabled: var(--color-gray-200); + + // Component Styles --bg: var(--color-gray-950); --text: var(--color-gray-50); --text-secondary: var(--color-gray-400); - --nav-bg: hsl(210, 60%, 30%); - --nav-bg-hover: hsl(210, 60%, 10%); - --nav-bg-visited: hsl(250, 60%, 30%); - --link: hsl(210, 60%, 60%); - --link-hover: hsl(210, 60%, 60%); - --link-visited: hsl(250, 60%, 70%); + + --nav-bg: var(--color-indigo-800); + --nav-bg-hover: var(--color-indigo-950); + --nav-bg-visited: var(--color-purple-800); + + --link: var(--color-indigo-400); + --link-hover: var(--color-indigo-200); + --link-visited: var(--color-purple-400); + --station-bg: var(--color-gray-950); --station-bg-hover: var(--color-gray-600); --station-border: var(--color-gray-600); --station-border-hover: var(--color-gray-400); --elevator-bg: var(--color-gray-900); + + --backdrop: rgb(100% 100% 100% / 20%); } --elevator-list-gap: 3rem; @@ -57,27 +94,84 @@ $mediumBreakpoint: 800px; --space-m: 0.5rem; --space-l: 1rem; --space-xl: 2rem; + --space-2xl: 3rem; +} + +body { + &.has-dialog { + overflow: hidden; + } +} + +.dialogs { + display: none; + position: fixed; + overflow-y: auto; + max-width: 100vw; + padding: 1em; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 99; + background: var(--backdrop); + + &.active { + display: block; + } + + + [role="dialog"] { + box-sizing: border-box; + max-width: 800px; + margin: 0 auto; + padding: var(--space-xl); + padding-bottom: var(--space-2xl); + background-color: var(--bg); + color: var(--text); + overflow: hidden; + position: absolute; + right: 0; + bottom: 0; + left: 0; + border-radius: var(--item-radius) var(--item-radius) 0 0; + + @media screen and (min-width: $mobileBreakpoint) { + position: relative; + border-radius: var(--item-radius); + } + + button { + &.close-modal { + position: absolute; + top: 0; + right: 0; + border-radius: 0 0 0 var(--btn-radius); + border: none; + } + } + } } .hidden { - display: none; + display: none !important; } .text-red { - color: #e32d2d; + color: var(--color-red-800); } .text-orange { - color: #cc6300; + color: var(--color-orange-500); } .text-green { - color: #19942e; + color: var(--color-green-600); } body { font-family: system-ui, ui-sans-serif, sans-serif; - font-size: 1.2rem; + font-size: 1rem; // colors background-color: var(--bg); @@ -92,7 +186,7 @@ a { color: var(--link); text-decoration: none; - &:hover { + &:hover, &:visited:hover { text-decoration: underline; color: var(--link-hover); } @@ -114,20 +208,20 @@ nav { li { a { font-weight: bold; - color: var(--nav-text); + color: var(--text); text-decoration: none; background-color: var(--nav-bg); padding: var(--space-m) var(--space-l); cursor: pointer; - &:hover { + &:hover, &:visited:hover { text-decoration: underline; background-color: var(--nav-bg-hover); } &:visited { - color: var(--nav-text-visited); + color: var(--text); background-color: var(--nav-bg-visited); } } @@ -140,6 +234,13 @@ main { max-width: 960px; } +footer { + display: flex; + justify-content: center; + margin: 1em auto; + max-width: 960px; +} + button, input { background-color: var(--station-bg); color: var(--text); @@ -158,16 +259,140 @@ button { background-color: var(--station-bg-hover); border-color: var(--station-border-hover); } + + &[aria-pressed='true'] { + background-color: var(--nav-bg-visited); + } + + &.size-s { + font-size: 0.8rem; + padding: var(--space-s) var(--space-m); + } + + &.size-m { + padding: var(--space-m) var(--space-l); + } + + &.size-l { + font-size: 1.5rem; + padding: var(--space-m) var(--space-l); + } } -div.filters { +div#updateInfo { display: flex; + align-items: center; gap: var(--space-m); - justify-items: stretch; + justify-content: end; + flex-direction: column; + margin-bottom: var(--space-l); - button, input { + @media screen and (min-width: $mobileBreakpoint) { + flex-direction: row; + } + + #oldDataWarning { + color: var(--color-red-600); + font-weight: bold; + } +} + +div#filters { + display: flex; + flex-wrap: wrap; + gap: var(--space-m); + justify-content: center; + + > * { + width: 100%; + justify-content: center; + text-align: center; + @media screen and (min-width: $mobileBreakpoint) { + width: auto; + min-width: 0; + } + } + + input#searchStation { + flex-grow: 1; + font-size: 1.5rem; + min-width: 10ch; + } + + button#stationsNearMe { font-size: 1.5rem; } + + div#typeFilter { + font-size: 1.5rem; + display: flex; + justify-content: center; + flex-shrink: 0; + min-height: var(--space-2xl); + gap: var(--space-m); + + button.typeChip { + flex-shrink: 0; + padding: 0; + height: var(--space-2xl); + aspect-ratio: 1/1; + justify-content: center; + align-items: center; + position: relative; + overflow: hidden; + border-radius: 9999px; + + &[data-type="U"], &[data-type="S"], &[data-type="A"], &[data-type="R"] { + border: solid 2px var(--text); + + &[data-pressed="false"] { + background-color: var(--color-gray-500); + color: var(--color-type-disabled); + + &[data-type="U"] { + background-color: var(--bg-type-u-disabled); + } + + &[data-type="S"] { + background-color: var(--bg-type-s-disabled); + } + + &[data-type="A"] { + background-color: var(--bg-type-a-disabled); + } + + &[data-type="R"] { + background-color: var(--bg-type-r-disabled); + } + + &::after { + position: absolute; + content: ''; + width: 144%; + height: 0.4rem; + background-color: var(--color-gray-700); + transform: rotate(-45deg); + } + } + } + } + } +} + +button#initialLoad { + font-size: 1.5rem; +} + +button.loadOSM { + width: fit-content; +} + +div#errorMessage { + border: solid 2px var(--color-red-600); + border-radius: var(--item-radius); + padding: var(--space-xl) var(--space-xl); + margin-top: var(--space-l); + font-size: 1.5rem; } ul#stationList { @@ -177,6 +402,12 @@ ul#stationList { flex-direction: column; gap: var(--space-m); + li#filterInfo { + text-align: center; + padding: var(--space-l) var(--space-xl); + font-style: italic; + } + > li.station { background-color: var(--station-bg); border-radius: var(--item-radius); @@ -189,8 +420,22 @@ ul#stationList { display: flex; gap: var(--space-m); - div.typeList { - flex-shrink: 0; + div.symbol { + position: relative; + + div.typeList { + flex-shrink: 0; + } + + div.distance { + position: absolute; + width: 100%; + bottom: -2lh; + + text-align: center; + color: var(--text-secondary); + font-size: 0.9em; + } } @@ -233,19 +478,28 @@ ul#stationList { > details { width: 100%; + display: flex; + flex-direction: column; summary { + width: 100%; list-style: none; - border: solid 2px var(--station-border); display: inline-flex; - padding: var(--space-m) var(--space-l); - border-radius: var(--btn-radius); cursor: pointer; margin-left: calc(var(--space-m) + var(--type-icon-size)); + > span { + border: solid 2px var(--station-border); + display: inline-flex; + padding: var(--space-m) var(--space-l); + border-radius: var(--btn-radius); + } + &:hover { - background-color: var(--station-bg-hover); - border-color: var(--station-border-hover); + > span { + background-color: var(--station-bg-hover); + border-color: var(--station-border-hover); + } } &::after { @@ -269,6 +523,22 @@ ul#stationList { margin-bottom: 1rem; } + > div.comment { + position: relative; + z-index: 20; + display: inline-flex; + margin-left: calc(var(--space-m) + var(--type-icon-size)); + padding-bottom: var(--space-m); + border-bottom: solid 2px var(--station-border); + + > p { + display: block; + border: solid 2px var(--nav-bg); + border-radius: var(--btn-radius); + padding: var(--space-l); + } + } + > ul { padding: 0; list-style: none; @@ -283,18 +553,29 @@ ul#stationList { display: flex; align-items: start; flex-wrap: nowrap; - padding: var(--space-m); gap: var(--space-m); position: relative; - margin: 0 1rem 1rem; + margin: 0 var(--space-s) var(--space-s); + padding: var(--space-s); @media screen and (min-width: $mobileBreakpoint) { - margin: 0 3rem 1rem; + margin: 0 var(--space-2xl) 1rem; + padding: var(--space-m); + padding-right: var(--space-2xl); } // elevator icon - > span { + > .stateIcon { flex-shrink: 0; + display: none; + @media screen and (min-width: $mobileBreakpoint) { + display: inline; + } + } + + // space above first elevator + &:first-child { + padding-top: var(--space-l); } // divider between elevators @@ -325,16 +606,60 @@ ul#stationList { grid-column: 1 / -1; } - .lineList { + div.firstRow { + grid-column: 1 / -1; display: flex; gap: var(--space-m); - align-items: center; + + &.hiddenOnDesktop { + @media screen and (min-width: $mobileBreakpoint) { + display: none; + } + } + + .stateIcon { + @media screen and (min-width: $mobileBreakpoint) { + display: none; + } + } + + + .lineList { + display: flex; + gap: var(--space-m); + align-items: center; + min-height: var(--space-2xl); + } + } + + + hr { grid-column: 1 / -1; + width: 100%; + margin: 0; + border: none; + border-bottom: dashed 2px var(--station-border); } .osm { grid-column: 1 / -1; color: var(--text-secondary); + border: dashed 2px var(--station-border); + border-radius: var(--item-radius); + padding: var(--space-l); + + .osmHeading { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--space-l); + margin-bottom: var(--space-l); + + h4 { + margin: 0; + } + } + dl { div { @@ -412,14 +737,16 @@ ul#stationList { font-weight: bold; min-width: 15ch; - @media (max-width: $mobileBreakpoint) { + @media (max-width: $smallBreakpoint) { width: 100%; } } dd { - flex-shrink: 0; - margin-left: 3rem; + width: 0; + flex-grow: 1; + + margin-left: var(--space-2xl); @media screen and (min-width: $mobileBreakpoint) { margin-left: 1rem; @@ -434,79 +761,84 @@ ul#stationList { } } -span.lineChip, span.typeChip { +span.lineChip, span.typeChip, button.typeChip { font-weight: bold; font-size: 0.8em; text-align: center; box-sizing: border-box; &[data-type="U"] { - background-color: #006ab3; - color: #ffffff; + background-color: var(--bg-type-u); + color: var(--color-white); &[data-line="U1"] { - background-color: #006ab3; + background-color: var(--color-line-U1); } &[data-line="U2"] { - background-color: #e2001a; + background-color: var(--color-line-U2); } &[data-line="U3"] { - background-color: #ffdd00; - color: #000000; + background-color: var(--color-line-U3); + color: var(--color-black); } &[data-line="U4"] { - background-color: #0098a1; + background-color: var(--color-line-U4); + } + + &[data-line="U5"] { + background-color: var(--color-line-U5); } } &[data-type="S"] { - background-color: #1a962b; - color: #ffffff; + background-color: var(--bg-type-s); + color: var(--color-white); &[data-line="S1"] { - background-color: #1a962b; + background-color: var(--color-line-S1); } &[data-line="S2"] { - background-color: #b51143; + background-color: var(--color-line-S2); } &[data-line="S3"] { - background-color: #622181; + background-color: var(--color-line-S3); } &[data-line="S4"] { - background-color: #be148e; + background-color: var(--color-line-S4); } &[data-line="S5"] { - background-color: #0089bb; + background-color: var(--color-line-S5); } &[data-line="S6"] { - background-color: #d3da00; - color: #000000; + background-color: var(--color-line-S6); + color: var(--color-black); } } &[data-type="A"] { - background-color: #f39100; - color: #ffffff; + background-color: var(--bg-type-a); + color: var(--color-white); } &[data-type="R"] { - background-color: #000000; - color: #ffffff; + background-color: var(--bg-type-r); + color: var(--color-white); @media (prefers-color-scheme: dark) { - border: solid 2px #ffffff; + border: solid 2px var(--color-white); } } } + span.lineChip { padding: 0.3em 0.6em; min-width: 4ch; @@ -641,4 +973,29 @@ span[data-icon] { &[data-icon="location-searching"] { mask-image: url(/md_icons/location-searching.svg); } + + &[data-icon="load"] { + mask-image: url(/md_icons/load.svg); + } + + &[data-icon="close"] { + mask-image: url(/md_icons/close.svg); + } + + &.spinner { + animation-delay: 0s; + animation-direction: normal; + animation-duration: 1s; + animation-fill-mode: none; + animation-iteration-count: infinite; + animation-name: spinner; + animation-play-state: running; + animation-timing-function: linear + } +} + +@keyframes spinner { + to { + transform: rotate(360deg); + } } diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..8b19416 --- /dev/null +++ b/sw.js @@ -0,0 +1,101 @@ +const version = '0.6.4' +const consolePrefix = `[SW v${version}] ` + +const cacheName = `hvvstuhl-${version}`; +const contentToCache = [ + '/', + '/index.html', + '/about.html', + '/main.js', + '/elevators.js', + '/style.css', + '/icons/192.png', + '/icons/512.png', + '/icons/512-maskable.png', + '/icons/512-transparent.png', + '/icons/favicon.ico', + '/icons/favicon.svg', + '/icons/favicon-maskable.svg', + '/icons/favicon-transparent.svg', + '/md_icons/bicycle.svg', + '/md_icons/braille.svg', + '/md_icons/door_sliding.svg', + '/md_icons/elevator.svg', + '/md_icons/elevator-slash.svg', + '/md_icons/fit_width.svg', + '/md_icons/height.svg', + '/md_icons/info.svg', + '/md_icons/load.svg', + '/md_icons/location.svg', + '/md_icons/location-searching.svg', + '/md_icons/speaker.svg', + '/md_icons/up-down.svg', + '/md_icons/wheelchair.svg', + '/md_icons/width.svg', +]; + +// Service worker Install: Cache all files +self.addEventListener("install", (e) => { + console.log(`${consolePrefix}Wird installiert....`); + e.waitUntil( + (async () => { + const cache = await caches.open(cacheName); + console.log(`${consolePrefix} Cache wird aufgebaut...`); + await cache.addAll(contentToCache); + console.log(`${consolePrefix} > FERTIG`); + console.log(`${consolePrefix} Versuche Wartezeit zu überspringen...`); + await self.skipWaiting(); + console.log(`${consolePrefix} > Erfolgreich`); + })(), + ); +}); + + +// delete old caches +const deleteCache = async (key) => { + await caches.delete(key); +}; +const deleteOldCaches = async () => { + const keyList = await caches.keys(); + const cachesToDelete = keyList.filter((key) => key !== cacheName); + await Promise.all(cachesToDelete.map(deleteCache)); +}; +self.addEventListener("activate", (event) => { + event.waitUntil( + (async () => { + await deleteOldCaches(); + console.log(`${consolePrefix}Versuche Clients zu beanspruchen...`); + await self.clients.claim(); + console.log(`${consolePrefix} > Erfolgreich`); + })(), + ); +}); + + + +// Respond with data from cache when offline +self.addEventListener("fetch", (e) => { + e.respondWith( + (async () => { + const path = new URL(e.request.url).pathname + + if(contentToCache.includes(path)){ + console.log(`${consolePrefix}Anfrage: ${path}`); + + const r = await caches.match(e.request); + if (r) { + console.log(`${consolePrefix} > Im Cache gefunden.`); + return r; + } + const response = await fetch(e.request); + const cache = await caches.open(cacheName); + console.log(`${consolePrefix} > Nicht gefunden. Aktualisiere Cache: ${path}`); + await cache.put(e.request, response.clone()); + return response; + }else{ + console.log(`${consolePrefix}Anfrage übersprungen: ${e.request.url}`); + return await fetch(e.request); + } + })(), + ); +}); \ No newline at end of file