diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d5b7d27f9..5ea443e80 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,6 +15,7 @@ repos:
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-json
+ exclude: "layouts/_default/home.transitousmapping.json$"
- id: check-merge-conflict
- id: check-shebang-scripts-are-executable
- id: check-symlinks
diff --git a/archetypes/country/index.de.md b/archetypes/country/index.de.md
index 2f8fb99e3..b1ae98cf2 100644
--- a/archetypes/country/index.de.md
+++ b/archetypes/country/index.de.md
@@ -3,8 +3,9 @@ draft: false
title: "{{ .File.ContentBaseName | title }}" # Ändere den Name auf den deutschen Ländernamen
country: "{{ .File.ContentBaseName }}"
params:
+ iso_code: # ISO 3166-1 alpha-2 Code, z. B. DE
operators_without_fip:
- - # Liste Betreiber, die kein FIP akzeptieren
+ - # Betreibername
---
diff --git a/archetypes/country/index.en.md b/archetypes/country/index.en.md
index 9a845c761..9a346b5c1 100644
--- a/archetypes/country/index.en.md
+++ b/archetypes/country/index.en.md
@@ -3,8 +3,9 @@ draft: false
title: "{{ .File.ContentBaseName | title }}" # Change the name to the English country name
country: "{{ .File.ContentBaseName }}"
params:
+ iso_code: # ISO 3166-1 alpha-2 code, e.g. DE
operators_without_fip:
- - # List operators without FIP here
+ - # Operator name
---
diff --git a/archetypes/country/index.fr.md b/archetypes/country/index.fr.md
index 06df884ac..175a70629 100644
--- a/archetypes/country/index.fr.md
+++ b/archetypes/country/index.fr.md
@@ -3,8 +3,9 @@ draft: false
title: "{{ .File.ContentBaseName | title }}" # Change le nom par le nom du pays français
country: "{{ .File.ContentBaseName }}"
params:
+ iso_code: # Code ISO 3166-1 alpha-2, p.ex. DE
operators_without_fip:
- - # Listez ici les opérateurs ne participant pas au FIP
+ - # Nom de l'opérateur
---
diff --git a/assets/js/anchorlinks.js b/assets/js/anchorlinks.js
index 6cce91868..3bc3f2b4a 100644
--- a/assets/js/anchorlinks.js
+++ b/assets/js/anchorlinks.js
@@ -1,7 +1,9 @@
+const snackbar = document.getElementById("snackbar");
+const snackbarHome = snackbar.parentNode;
+const snackbarNextSibling = snackbar.nextSibling;
+
function initAnchorlinkEventListener() {
const anchorLinks = document.querySelectorAll(".a-anchorlink__link");
- const snackbar = document.getElementById("snackbar");
- const snackbarButton = document.getElementById("snackbar-button");
anchorLinks.forEach((element) => {
element.addEventListener("click", () => {
@@ -19,32 +21,53 @@ function initAnchorlinkEventListener() {
});
});
});
+}
+
+function initSnackbarCloseListener() {
+ const snackbarButton = document.getElementById("snackbar-button");
+ if (!snackbarButton) return;
snackbarButton.addEventListener("click", () => {
closeSnackbar();
});
}
-function showSnackbar() {
+export function showSnackbar() {
+ const openDialog = document.querySelector("dialog[open]");
+ if (openDialog && snackbar.parentNode !== openDialog) {
+ openDialog.append(snackbar);
+ } else if (!openDialog && snackbar.parentNode !== snackbarHome) {
+ snackbarHome.insertBefore(snackbar, snackbarNextSibling);
+ }
+
snackbar.setAttribute("aria-hidden", "false");
snackbar.classList.add("a-snackbar--show");
+ if (snackbar.showPopover) snackbar.showPopover();
setTimeout(closeSnackbar, 5000);
}
-function closeSnackbar() {
+export function closeSnackbar() {
snackbar.setAttribute("aria-hidden", "true");
snackbar.classList.remove("a-snackbar--show");
+ if (snackbar.hidePopover && snackbar.matches(":popover-open")) {
+ snackbar.hidePopover();
+ }
+ if (snackbar.parentNode !== snackbarHome) {
+ snackbarHome.insertBefore(snackbar, snackbarNextSibling);
+ }
}
if (document.readyState === "interactive") {
if (document.querySelectorAll(".a-anchorlink__link").length) {
initAnchorlinkEventListener();
}
+ initSnackbarCloseListener();
} else {
window.addEventListener("DOMContentLoaded", () => {
if (document.querySelectorAll(".a-anchorlink__link").length) {
initAnchorlinkEventListener();
}
+ initSnackbarCloseListener();
});
}
diff --git a/assets/js/dialog.js b/assets/js/dialog.js
index 76932ae97..4081ea9cf 100644
--- a/assets/js/dialog.js
+++ b/assets/js/dialog.js
@@ -24,34 +24,40 @@ function openDialogOnHash() {
}
}
-function initDialogs() {
- document.querySelectorAll("dialog").forEach((dialog) => {
- dialog.addEventListener("click", (e) => {
- if (e.target === dialog) {
- dialog.close();
- }
- });
-
- const closeButton = getCloseButton(dialog);
- if (closeButton) {
- closeButton.addEventListener("click", () => dialog.close());
+export function bindDialog(dialog) {
+ dialog.addEventListener("click", (e) => {
+ if (e.target === dialog) {
+ dialog.close();
}
});
- document.querySelectorAll("[data-dialog-trigger]").forEach((trigger) => {
- const handler = (e) => {
- if (e.type === "click" || (e.type === "keydown" && e.key === "Enter")) {
- e.preventDefault();
- const dialogId = trigger.getAttribute("data-dialog-trigger");
- openDialog(dialogId);
- }
- };
-
- trigger.addEventListener("click", handler);
- trigger.addEventListener("keydown", handler);
- });
+ const closeButton = getCloseButton(dialog);
+ if (closeButton) {
+ closeButton.addEventListener("click", () => dialog.close());
+ }
+}
+
+export function bindDialogTrigger(trigger) {
+ const handler = (e) => {
+ if (e.type === "click" || (e.type === "keydown" && e.key === "Enter")) {
+ e.preventDefault();
+ const dialogId = trigger.getAttribute("data-dialog-trigger");
+ openDialog(dialogId);
+ }
+ };
+
+ trigger.addEventListener("click", handler);
+ trigger.addEventListener("keydown", handler);
+}
+
+export function initDialogsIn(root) {
+ root.querySelectorAll("dialog").forEach(bindDialog);
+ root.querySelectorAll("[data-dialog-trigger]").forEach(bindDialogTrigger);
+}
+
+function initDialogs() {
+ initDialogsIn(document);
- // Initialize hash check
openDialogOnHash();
window.addEventListener("hashchange", openDialogOnHash);
}
diff --git a/assets/js/main.js b/assets/js/main.js
index 250f2f335..24410a683 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -11,3 +11,4 @@ import "./expander.js";
import "./dialog.js";
import "./fipValidityComparison.js";
import "./search.js";
+import "./routePlanner.js";
diff --git a/assets/js/routePlanner.js b/assets/js/routePlanner.js
new file mode 100644
index 000000000..a58055514
--- /dev/null
+++ b/assets/js/routePlanner.js
@@ -0,0 +1,698 @@
+import {
+ client,
+ geocode,
+ plan,
+ reverseGeocode,
+} from "@motis-project/motis-client";
+import { bindDialog, bindDialogTrigger } from "./dialog.js";
+import { showSnackbar } from "./anchorlinks.js";
+
+const countryCache = new Map();
+
+async function getCountryForPlace(place) {
+ if (!place?.lat || !place?.lon) return null;
+ const key = `${place.lat},${place.lon}`;
+ if (countryCache.has(key)) return countryCache.get(key);
+ try {
+ const res = await reverseGeocode({ query: { place: key, numResults: 1 } });
+ const country = res.data?.[0]?.country || null;
+ countryCache.set(key, country);
+ return country;
+ } catch {
+ return null;
+ }
+}
+
+client.setConfig({
+ baseUrl: "https://api.transitous.org",
+ querySerializer: (params) => {
+ const parts = [];
+ for (const [key, value] of Object.entries(params)) {
+ if (value === undefined || value === null) continue;
+ if (Array.isArray(value)) {
+ parts.push(`${key}=${value.map(encodeURIComponent).join(",")}`);
+ } else {
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
+ }
+ }
+ return parts.join("&");
+ },
+});
+
+const DEBOUNCE_MS = 300;
+const MIN_QUERY_LEN = 2;
+
+let fromPlace = null;
+let toPlace = null;
+
+let transitousMapping = null;
+
+async function loadData() {
+ const lang = window.routePlannerConfig.lang;
+ const mappingRes = await fetch(`/${lang}/transitous-mapping.json`);
+ transitousMapping = await mappingRes.json();
+}
+
+function evalQuery(query, leg) {
+ try {
+ const {
+ agencyId,
+ agencyName,
+ routeShortName,
+ displayName,
+ mode,
+ routeId,
+ tripShortName,
+ } = leg;
+ const fromStopId = leg.from?.stopId || leg.from?.id || "";
+ const toStopId = leg.to?.stopId || leg.to?.id || "";
+ const fromName = leg.from?.name || "";
+ const toName = leg.to?.name || "";
+ const fromCountry =
+ (leg.from?.lat &&
+ leg.from?.lon &&
+ countryCache.get(`${leg.from.lat},${leg.from.lon}`)) ||
+ "";
+ const toCountry =
+ (leg.to?.lat &&
+ leg.to?.lon &&
+ countryCache.get(`${leg.to.lat},${leg.to.lon}`)) ||
+ "";
+ return Function(
+ "agencyId",
+ "agencyName",
+ "routeShortName",
+ "displayName",
+ "mode",
+ "routeId",
+ "tripShortName",
+ "fromStopId",
+ "toStopId",
+ "fromName",
+ "toName",
+ "fromCountry",
+ "toCountry",
+ `"use strict"; return (${query});`,
+ )(
+ agencyId,
+ agencyName,
+ routeShortName,
+ displayName,
+ mode,
+ routeId,
+ tripShortName,
+ fromStopId,
+ toStopId,
+ fromName,
+ toName,
+ fromCountry,
+ toCountry,
+ );
+ } catch {
+ return false;
+ }
+}
+
+function matchLeg(leg) {
+ if (!transitousMapping) return null;
+ const allEntries = [];
+ for (const op of transitousMapping) {
+ for (const entry of op.mapping) {
+ allEntries.push({ ...entry, operatorSlug: op.operator });
+ }
+ }
+ allEntries.sort((a, b) => (b.priority || 0) - (a.priority || 0));
+ for (const entry of allEntries) {
+ if (evalQuery(entry.query, leg)) {
+ return {
+ operatorSlug: entry.operatorSlug,
+ categoryId: entry.category || null,
+ fipAccepted: entry.fipAccepted ?? null,
+ reservationRequired: entry.reservationRequired ?? null,
+ matchedQuery: entry.query,
+ country: entry.country || null,
+ message: entry.message || null,
+ };
+ }
+ }
+ return null;
+}
+
+function getFipStatus(leg) {
+ const cfg = window.routePlannerConfig;
+ const debug = {
+ from: leg.from?.name || null,
+ fromId: leg.from?.stopId || leg.from?.id || null,
+ fromCountry: null,
+ to: leg.to?.name || null,
+ toId: leg.to?.stopId || leg.to?.id || null,
+ toCountry: null,
+ startTime: leg.scheduledStartTime
+ ? new Date(leg.scheduledStartTime).toLocaleString()
+ : null,
+ endTime: leg.scheduledEndTime
+ ? new Date(leg.scheduledEndTime).toLocaleString()
+ : null,
+ agencyId: leg.agencyId || null,
+ agencyName: leg.agencyName || null,
+ routeShortName: leg.routeShortName || null,
+ displayName: leg.displayName || null,
+ mode: leg.mode || null,
+ routeId: leg.routeId || null,
+ tripShortName: leg.tripShortName || null,
+ matchedQuery: null,
+ };
+
+ const match = matchLeg(leg);
+ if (!match) {
+ return {
+ status: "unknown",
+ label: cfg.labels.fipUnknown,
+ categoryUrl: null,
+ operatorSlug: null,
+ debug,
+ };
+ }
+
+ debug.matchedQuery = match.matchedQuery;
+ const {
+ operatorSlug,
+ categoryId,
+ fipAccepted: matchedFipAccepted,
+ reservationRequired,
+ country,
+ message,
+ } = match;
+ const isNoFip = operatorSlug === "no-fip";
+ const operatorUrl = isNoFip ? null : `/${cfg.lang}/operator/${operatorSlug}/`;
+ const categoryUrl = isNoFip
+ ? country
+ ? `/${cfg.lang}/country/${country}/#operators-without-fip-title`
+ : null
+ : categoryId
+ ? `/${cfg.lang}/operator/${operatorSlug}/#${categoryId}`
+ : operatorUrl;
+
+ if (matchedFipAccepted === false || matchedFipAccepted === "false") {
+ return {
+ status: "not-accepted",
+ label: cfg.labels.fipNotAccepted,
+ categoryUrl,
+ operatorSlug,
+ reservationRequired,
+ message,
+ debug,
+ };
+ }
+ if (matchedFipAccepted === "partially") {
+ return {
+ status: "partial",
+ label: cfg.labels.fipPartial,
+ categoryUrl,
+ operatorSlug,
+ reservationRequired,
+ message,
+ debug,
+ };
+ }
+ if (matchedFipAccepted === true || matchedFipAccepted === "true") {
+ return {
+ status: "accepted",
+ label: cfg.labels.fipAccepted,
+ categoryUrl,
+ operatorSlug,
+ reservationRequired,
+ message,
+ debug,
+ };
+ }
+ return {
+ status: "unknown",
+ label: cfg.labels.fipUnknown,
+ categoryUrl,
+ operatorSlug,
+ reservationRequired,
+ message,
+ debug,
+ };
+}
+
+function formatTime(isoString) {
+ if (!isoString) return "";
+ const d = new Date(isoString);
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
+}
+
+function formatDuration(seconds) {
+ const h = Math.floor(seconds / 3600);
+ const m = Math.floor((seconds % 3600) / 60);
+ if (h > 0) return `${h}h ${m}min`;
+ return `${m}min`;
+}
+
+let fipDebugCounter = 0;
+let legWarningCounter = 0;
+
+function renderInternationalWarning(fromCode, toCode) {
+ const cfg = window.routePlannerConfig;
+ if (!fromCode || !toCode || fromCode === toCode) return "";
+
+ const countries = [...new Set([fromCode, toCode])]
+ .map((code) => {
+ const entry = cfg.countryCodeMap[code];
+ if (!entry) return null;
+ return `${entry.name}`;
+ })
+ .filter(Boolean);
+
+ if (countries.length === 0) return "";
+ return `
${cfg.labels.internationalJourney} ${countries.join(", ")}
`;
+}
+
+function renderFipBadge(fipStatus, dialogId) {
+ const cfg = window.routePlannerConfig;
+ if (!dialogId) dialogId = `fip-debug-${++fipDebugCounter}`;
+
+ const rows = Object.entries(fipStatus.debug)
+ .map(
+ ([k, v]) =>
+ `| ${k} | ${v !== null ? v : "—"} |
`,
+ )
+ .join("");
+ const dialogHtml = `
+ `;
+
+ let reservationBadge = "";
+ if (
+ fipStatus.reservationRequired === true ||
+ fipStatus.reservationRequired === "true"
+ ) {
+ reservationBadge = ` ${cfg.labels.reservationRequired}`;
+ } else if (fipStatus.reservationRequired === "partially") {
+ reservationBadge = ` ${cfg.labels.reservationPartiallyRequired}`;
+ }
+ let badgeHtml = `${fipStatus.label}${reservationBadge}`;
+ badgeHtml += ` ${dialogHtml}`;
+ if (fipStatus.categoryUrl) {
+ badgeHtml += ` ${cfg.labels.viewInFipGuide}`;
+ }
+ return badgeHtml;
+}
+
+function buildDebugText(dialog) {
+ const rows = dialog.querySelectorAll(".o-route-planner__fip-debug-key");
+ return Array.from(rows)
+ .map((keyCell) => {
+ const key = keyCell.textContent;
+ const valCell = keyCell.nextElementSibling;
+ return `${key}: ${valCell ? valCell.textContent : "—"}`;
+ })
+ .join("\n");
+}
+
+function buildGitHubIssueUrl(dialog) {
+ const cfg = window.routePlannerConfig;
+ const body = buildDebugText(dialog);
+ return `${cfg.gitHubUrl}/issues/new?title=Route+planner+issue&labels=content,route-planner&body=${encodeURIComponent("```yaml\n" + body + "\n```")}`;
+}
+
+function bindDebugActions(dialog) {
+ const copyButton = dialog.querySelector('[data-debug-action="copy"]');
+ const githubButton = dialog.querySelector('[data-debug-action="github"]');
+
+ if (copyButton) {
+ copyButton.addEventListener("click", () => {
+ const text = buildDebugText(dialog);
+ navigator.clipboard.writeText(text).then(() => {
+ showSnackbar();
+ });
+ });
+ }
+
+ if (githubButton) {
+ githubButton.addEventListener("click", () => {
+ window.open(buildGitHubIssueUrl(dialog), "_blank", "noopener,noreferrer");
+ });
+ }
+}
+
+function renderLeg(leg) {
+ const cfg = window.routePlannerConfig;
+
+ if (leg.mode === "WALK" || leg.mode === "FOOT") {
+ const mins = Math.round(leg.duration / 60);
+ return `
+
+
🚶
+
+ ${cfg.labels.walk} ${mins}min
+
+
`;
+ }
+
+ const agencyName = leg.agencyName || "";
+ const fipStatus = getFipStatus(leg);
+ const legLabel = leg.displayName || leg.routeShortName || "";
+ const depTime = formatTime(leg.scheduledStartTime);
+ const arrTime = formatTime(leg.scheduledEndTime);
+ const headsign = leg.headsign ? ` → ${leg.headsign}` : "";
+ const routeColor = leg.routeColor
+ ? `#${leg.routeColor}`
+ : "var(--link-default)";
+ const textColor = leg.routeTextColor ? `#${leg.routeTextColor}` : "#fff";
+ const fromPlatform = leg.from?.platformCode
+ ? ` ${cfg.labels.platform} ${leg.from.platformCode}`
+ : "";
+ const toPlatform = leg.to?.platformCode
+ ? ` ${cfg.labels.platform} ${leg.to.platformCode}`
+ : "";
+
+ const warningId = ++legWarningCounter;
+ const dialogId = `fip-debug-${++fipDebugCounter}`;
+ Promise.all([getCountryForPlace(leg.from), getCountryForPlace(leg.to)]).then(
+ ([fromCode, toCode]) => {
+ setTimeout(() => {
+ const el = document.getElementById(`leg-warning-${warningId}`);
+ if (el) el.outerHTML = renderInternationalWarning(fromCode, toCode);
+ const dialog = document.getElementById(dialogId);
+ if (dialog) {
+ const update = (key, val) => {
+ const cell = dialog.querySelector(`[data-debug-key="${key}"]`);
+ if (cell) cell.textContent = val !== null ? val : "—";
+ };
+ update("fromCountry", fromCode);
+ update("toCountry", toCode);
+ }
+ }, 0);
+ },
+ );
+
+ return `
+
+
+ ${legLabel}
+
+
+
+ ${legLabel}${headsign}
+ ${agencyName ? `(${agencyName})` : ""}
+
+
+ ${depTime} ${leg.from?.name || ""}${fromPlatform}
+ ${formatDuration(leg.duration)}
+ ${arrTime} ${leg.to?.name || ""}${toPlatform}
+
+
+ ${fipStatus.message ? `
${fipStatus.message}
` : ""}
+
+ ${renderFipBadge(fipStatus, dialogId)}
+
+
+
`;
+}
+
+function renderItinerary(itinerary, index) {
+ const cfg = window.routePlannerConfig;
+ const scheduledStart = itinerary.legs?.[0]?.scheduledStartTime;
+ const scheduledEnd =
+ itinerary.legs?.[itinerary.legs.length - 1]?.scheduledEndTime;
+ const depTime = formatTime(scheduledStart);
+ const arrTime = formatTime(scheduledEnd);
+ const duration =
+ scheduledStart && scheduledEnd
+ ? formatDuration(
+ (new Date(scheduledEnd).getTime() -
+ new Date(scheduledStart).getTime()) /
+ 1000,
+ )
+ : formatDuration(itinerary.duration);
+
+ const transitLegs =
+ itinerary.legs?.filter((l) => l.mode !== "WALK" && l.mode !== "FOOT") || [];
+ const transfers = Math.max(0, transitLegs.length - 1);
+
+ const legsHtml = (itinerary.legs || [])
+ .map((leg) => renderLeg(leg))
+ .join('');
+
+ return `
+
+
+ ${depTime} – ${arrTime}
+ ${duration}
+ ${transfers > 0 ? `${transfers} ${cfg.labels.transfer}` : ""}
+
+
+ ${legsHtml}
+
+ `;
+}
+
+async function search() {
+ if (!fromPlace || !toPlace) return;
+
+ const cfg = window.routePlannerConfig;
+ const resultsEl = document.getElementById("route-planner-results");
+ const loadingEl = document.getElementById("route-planner-loading");
+ const errorEl = document.getElementById("route-planner-error");
+ const itinerariesEl = document.getElementById("route-planner-itineraries");
+
+ resultsEl.hidden = false;
+ loadingEl.hidden = false;
+ errorEl.hidden = true;
+ itinerariesEl.innerHTML = "";
+
+ const timeInput = document.getElementById("route-planner-time");
+ const timeValue = timeInput.value
+ ? new Date(timeInput.value).toISOString()
+ : new Date().toISOString();
+
+ try {
+ const response = await plan({
+ query: {
+ fromPlace: fromPlace.id,
+ toPlace: toPlace.id,
+ time: timeValue,
+ withFares: true,
+ numLegAlternatives: 3,
+ fastestDirectFactor: 1.5,
+ joinInterlinedLegs: false,
+ maxMatchingDistance: 250,
+ numItineraries: 5,
+ transitModes: [
+ "TRAM",
+ "FERRY",
+ "AIRPLANE",
+ "BUS",
+ "RAIL",
+ "FUNICULAR",
+ "AERIAL_LIFT",
+ ],
+ },
+ });
+
+ if (response.error) {
+ errorEl.textContent = cfg.labels.errorGeneric;
+ errorEl.hidden = false;
+ return;
+ }
+
+ const itineraries = response.data?.itineraries || [];
+ if (itineraries.length === 0) {
+ itinerariesEl.innerHTML = `${cfg.labels.noResults}
`;
+ return;
+ }
+
+ const allPlaces = itineraries.flatMap(
+ (it) =>
+ it.legs?.flatMap((leg) => [leg.from, leg.to].filter(Boolean)) || [],
+ );
+ await Promise.all(allPlaces.map((p) => getCountryForPlace(p)));
+
+ itinerariesEl.innerHTML = itineraries
+ .map((it, i) => renderItinerary(it, i))
+ .join("");
+ itinerariesEl.querySelectorAll("dialog").forEach((dialog) => {
+ bindDialog(dialog);
+ bindDebugActions(dialog);
+ });
+ itinerariesEl
+ .querySelectorAll("[data-dialog-trigger]")
+ .forEach((trigger) => bindDialogTrigger(trigger));
+ } catch (err) {
+ errorEl.textContent = cfg.labels.errorGeneric;
+ errorEl.hidden = false;
+ } finally {
+ loadingEl.hidden = true;
+ }
+}
+
+function setupAutocomplete(inputId, suggestionsId, onSelect) {
+ const input = document.getElementById(inputId);
+ const suggestions = document.getElementById(suggestionsId);
+ let debounce = null;
+ let activeIndex = -1;
+ let currentMatches = [];
+
+ function hideSuggestions() {
+ suggestions.hidden = true;
+ input.setAttribute("aria-expanded", "false");
+ activeIndex = -1;
+ }
+
+ function showSuggestions(matches) {
+ currentMatches = matches;
+ if (matches.length === 0) {
+ hideSuggestions();
+ return;
+ }
+ suggestions.innerHTML = matches
+ .map((m, i) => {
+ const area = m.areas?.[0]?.name
+ ? `${m.areas[0].name}`
+ : "";
+ return `${m.name}${area}`;
+ })
+ .join("");
+ suggestions.hidden = false;
+ input.setAttribute("aria-expanded", "true");
+ activeIndex = -1;
+ }
+
+ input.addEventListener("input", () => {
+ clearTimeout(debounce);
+ const q = input.value.trim();
+ if (q.length < MIN_QUERY_LEN) {
+ hideSuggestions();
+ onSelect(null);
+ return;
+ }
+ debounce = setTimeout(async () => {
+ try {
+ const res = await geocode({ query: { text: q, numResults: 20 } });
+ showSuggestions((res.data || []).filter((r) => r.type === "STOP"));
+ } catch {
+ hideSuggestions();
+ }
+ }, DEBOUNCE_MS);
+ });
+
+ suggestions.addEventListener("mousedown", (e) => {
+ const li = e.target.closest("[data-index]");
+ if (!li) return;
+ e.preventDefault();
+ const match = currentMatches[parseInt(li.dataset.index, 10)];
+ input.value = match.name;
+ onSelect(match);
+ hideSuggestions();
+ });
+
+ input.addEventListener("keydown", (e) => {
+ if (suggestions.hidden) return;
+ if (e.key === "ArrowDown") {
+ e.preventDefault();
+ activeIndex = Math.min(activeIndex + 1, currentMatches.length - 1);
+ updateActive();
+ } else if (e.key === "ArrowUp") {
+ e.preventDefault();
+ activeIndex = Math.max(activeIndex - 1, 0);
+ updateActive();
+ } else if (e.key === "Enter" && activeIndex >= 0) {
+ e.preventDefault();
+ const match = currentMatches[activeIndex];
+ input.value = match.name;
+ onSelect(match);
+ hideSuggestions();
+ } else if (e.key === "Escape") {
+ hideSuggestions();
+ }
+ });
+
+ function updateActive() {
+ const items = suggestions.querySelectorAll("[data-index]");
+ items.forEach((item, i) => {
+ item.classList.toggle(
+ "o-route-planner__suggestion-item--active",
+ i === activeIndex,
+ );
+ });
+ }
+
+ document.addEventListener("click", (e) => {
+ if (!input.contains(e.target) && !suggestions.contains(e.target)) {
+ hideSuggestions();
+ }
+ });
+}
+
+function setDefaultTime() {
+ const input = document.getElementById("route-planner-time");
+ const now = new Date();
+ now.setSeconds(0, 0);
+ input.value = now.toISOString().slice(0, 16);
+}
+
+function initRoutePlanner() {
+ if (!document.getElementById("route-planner-search")) return;
+
+ setDefaultTime();
+ loadData();
+
+ setupAutocomplete(
+ "route-planner-from",
+ "route-planner-from-suggestions",
+ (match) => {
+ fromPlace = match;
+ },
+ );
+
+ setupAutocomplete(
+ "route-planner-to",
+ "route-planner-to-suggestions",
+ (match) => {
+ toPlace = match;
+ },
+ );
+
+ document
+ .getElementById("route-planner-search")
+ .addEventListener("click", search);
+
+ document
+ .getElementById("route-planner-swap")
+ .addEventListener("click", () => {
+ const fromInput = document.getElementById("route-planner-from");
+ const toInput = document.getElementById("route-planner-to");
+ [fromInput.value, toInput.value] = [toInput.value, fromInput.value];
+ [fromPlace, toPlace] = [toPlace, fromPlace];
+ });
+}
+
+if (document.readyState !== "loading") {
+ initRoutePlanner();
+} else {
+ document.addEventListener("DOMContentLoaded", initRoutePlanner);
+}
diff --git a/assets/sass/anchorlink.scss b/assets/sass/anchorlink.scss
index c9a22e6d3..372589121 100644
--- a/assets/sass/anchorlink.scss
+++ b/assets/sass/anchorlink.scss
@@ -76,11 +76,13 @@
.a-snackbar {
position: fixed;
+ inset: auto auto 0 auto;
bottom: 0;
background-color: var(--bg-accent);
color: var(--color-onLight);
padding: 1.6rem;
border-radius: var(--border-radius-s);
+ border: none;
opacity: 0;
visibility: hidden;
transform: translateY(2rem);
diff --git a/assets/sass/main.scss b/assets/sass/main.scss
index 3aed6e2f5..5f4a26739 100644
--- a/assets/sass/main.scss
+++ b/assets/sass/main.scss
@@ -26,3 +26,4 @@
@import "dialog.scss";
@import "fip-validity.scss";
@import "404.scss";
+@import "routePlanner.scss";
diff --git a/assets/sass/routePlanner.scss b/assets/sass/routePlanner.scss
new file mode 100644
index 000000000..d6feba862
--- /dev/null
+++ b/assets/sass/routePlanner.scss
@@ -0,0 +1,432 @@
+.o-route-planner {
+ &__fields {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ max-width: 640px;
+ }
+
+ &__field-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+
+ label {
+ font-weight: 500;
+ font-size: 2rem;
+ }
+ }
+
+ &__autocomplete-wrapper {
+ position: relative;
+ }
+
+ &__suggestions {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ background: var(--bg-default);
+ border: 1px solid var(--border-visible);
+ border-radius: var(--border-radius-s);
+ box-shadow: var(--box-shadow);
+ list-style: none;
+ margin: 0.25rem 0 0;
+ padding: 0.25rem 0;
+ max-height: 320px;
+ overflow-y: auto;
+ }
+
+ &__suggestion-item {
+ padding: 0.65rem 1rem;
+ cursor: pointer;
+ font-size: 2rem;
+
+ &:hover,
+ &--active {
+ background: var(--bg-neutral);
+ }
+ }
+
+ &__suggestion-area {
+ display: block;
+ font-size: 1.4rem;
+ color: #666;
+
+ [data-theme="dark"] & {
+ color: #aaa;
+ }
+ }
+
+ &__swap {
+ align-self: flex-start;
+ margin-top: 0.25rem;
+ }
+
+ &__results {
+ margin-top: 1.5rem;
+ }
+
+ &__loading {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ color: var(--color-onLight, #000);
+ font-size: 1.5rem;
+ padding: 1rem 0;
+
+ &[hidden] {
+ display: none;
+ }
+ }
+
+ &__spinner {
+ display: inline-block;
+ width: 1.25rem;
+ height: 1.25rem;
+ border: 2px solid var(--bg-neutral);
+ border-top-color: var(--link-default);
+ border-radius: 50%;
+ animation: route-planner-spin 0.7s linear infinite;
+
+ @media (prefers-reduced-motion: reduce) {
+ animation: none;
+ border-top-color: var(--link-default);
+ }
+ }
+
+ &__error {
+ color: map-get($tag-colors, error);
+ padding: 0.75rem 1rem;
+ border: 1px solid currentColor;
+ border-radius: var(--border-radius-s);
+ background: rgba(map-get($tag-colors, error), 0.05);
+
+ [data-theme="dark"] & {
+ color: map-get($tag-colors-dark, error);
+ }
+ }
+
+ &__no-results {
+ color: #666;
+ font-style: italic;
+ }
+
+ &__itinerary {
+ border: 1px solid var(--border-visible);
+ border-radius: var(--border-radius-m);
+ margin-bottom: 1rem;
+ overflow: hidden;
+
+ &[open] > summary .a-icon {
+ transform: rotate(180deg);
+ }
+ }
+
+ &__itinerary-summary {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 0.875rem 1rem;
+ cursor: pointer;
+ background: var(--bg-neutral);
+ flex-wrap: wrap;
+ list-style: none;
+
+ &::-webkit-details-marker {
+ display: none;
+ }
+
+ &:hover {
+ background: darken(#ebe9e1, 5%);
+ }
+ }
+
+ &__itinerary-times {
+ font-weight: 600;
+ font-size: 1.75rem;
+ }
+
+ &__itinerary-duration {
+ color: #555;
+ font-size: 2rem;
+
+ [data-theme="dark"] & {
+ color: #aaa;
+ }
+ }
+
+ &__itinerary-transfers {
+ font-size: 1.5rem;
+ background: var(--bg-default);
+ border-radius: 999px;
+ padding: 0.1rem 0.5rem;
+ border: 1px solid var(--border-visible);
+ }
+
+ &__itinerary-legs {
+ padding: 1.25rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ }
+
+ &__leg-connector {
+ width: 2px;
+ height: 1.25rem;
+ background: var(--border-visible);
+ margin-left: 1.5rem;
+ }
+
+ &__leg {
+ display: flex;
+ gap: 1rem;
+ align-items: flex-start;
+
+ &--walk {
+ opacity: 0.7;
+ font-size: 1.5rem;
+ }
+
+ &--transit {
+ .o-route-planner__leg-info {
+ flex: 1;
+ min-width: 0;
+ }
+ }
+ }
+
+ &__leg-line {
+ flex-shrink: 0;
+ width: 8rem;
+ display: flex;
+ justify-content: center;
+ }
+
+ &__leg-badge {
+ display: inline-block;
+ background: var(--leg-color, var(--link-default));
+ color: var(--leg-text-color, #fff);
+ font-weight: 700;
+ font-size: 1.4rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: var(--border-radius-s);
+ white-space: nowrap;
+ max-width: 8rem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &__leg-icon {
+ flex-shrink: 0;
+ width: 3rem;
+ text-align: center;
+ font-size: 2rem;
+ }
+
+ &__leg-route {
+ font-size: 2rem;
+ margin-bottom: 0.3rem;
+ }
+
+ &__agency {
+ font-size: 1.4rem;
+ color: #666;
+ margin-left: 0.25rem;
+
+ [data-theme="dark"] & {
+ color: #aaa;
+ }
+ }
+
+ &__leg-times {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 1.5rem;
+ color: #444;
+ margin-bottom: 0.4rem;
+ flex-wrap: wrap;
+
+ [data-theme="dark"] & {
+ color: #ccc;
+ }
+ }
+
+ &__leg-duration {
+ font-style: italic;
+ flex-shrink: 0;
+ }
+
+ &__platform {
+ font-size: 1.3rem;
+ background: var(--bg-neutral);
+ border-radius: var(--border-radius-s);
+ padding: 0.1rem 0.3rem;
+ }
+
+ &__leg-walk {
+ font-size: 1.5rem;
+ color: #555;
+
+ [data-theme="dark"] & {
+ color: #aaa;
+ }
+ }
+
+ &__leg-fip {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ margin-top: 0.2rem;
+ }
+
+ &__fip-badge {
+ display: inline-block;
+ font-size: 1.3rem;
+ font-weight: 600;
+ padding: 0.2rem 0.6rem;
+ border-radius: 999px;
+ border: 1px solid currentColor;
+
+ &--accepted {
+ color: map-get($tag-colors, success);
+ background: rgba(map-get($tag-colors, success), 0.08);
+
+ [data-theme="dark"] & {
+ color: map-get($tag-colors-dark, success);
+ background: rgba(map-get($tag-colors-dark, success), 0.1);
+ }
+ }
+
+ &--partial {
+ color: map-get($tag-colors, warning);
+ background: rgba(map-get($tag-colors, warning), 0.08);
+
+ [data-theme="dark"] & {
+ color: map-get($tag-colors-dark, warning);
+ background: rgba(map-get($tag-colors-dark, warning), 0.1);
+ }
+ }
+
+ &--not-accepted {
+ color: map-get($tag-colors, error);
+ background: rgba(map-get($tag-colors, error), 0.08);
+
+ [data-theme="dark"] & {
+ color: map-get($tag-colors-dark, error);
+ background: rgba(map-get($tag-colors-dark, error), 0.1);
+ }
+ }
+
+ &--unknown {
+ color: map-get($tag-colors, info);
+ background: rgba(map-get($tag-colors, info), 0.08);
+
+ [data-theme="dark"] & {
+ color: map-get($tag-colors-dark, info);
+ background: rgba(map-get($tag-colors-dark, info), 0.1);
+ }
+ }
+
+ &--reservation-required {
+ color: map-get($tag-colors, error);
+ background: rgba(map-get($tag-colors, error), 0.08);
+
+ [data-theme="dark"] & {
+ color: map-get($tag-colors-dark, error);
+ background: rgba(map-get($tag-colors-dark, error), 0.1);
+ }
+ }
+
+ &--reservation-partial {
+ color: map-get($tag-colors, warning);
+ background: rgba(map-get($tag-colors, warning), 0.08);
+
+ [data-theme="dark"] & {
+ color: map-get($tag-colors-dark, warning);
+ background: rgba(map-get($tag-colors-dark, warning), 0.1);
+ }
+ }
+ }
+
+ &__fip-link {
+ font-size: 1.3rem;
+ }
+
+ &__international-warning {
+ font-size: 1.3rem;
+ margin-top: 0.4rem;
+ margin-bottom: 0.6rem;
+ padding: 0.4rem 0.75rem;
+ border-radius: var(--border-radius-s);
+ border-left: 3px solid map-get($tag-colors, warning);
+ background: rgba(map-get($tag-colors, warning), 0.08);
+ color: map-get($tag-colors, warning);
+
+ [data-theme="dark"] & {
+ border-left-color: map-get($tag-colors-dark, warning);
+ background: rgba(map-get($tag-colors-dark, warning), 0.1);
+ color: map-get($tag-colors-dark, warning);
+ }
+
+ a {
+ color: inherit;
+ font-weight: 600;
+ }
+ }
+
+ &__fip-info {
+ display: inline-flex;
+ align-items: center;
+ background: none;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+ color: var(--text-muted, currentColor);
+ opacity: 0.6;
+ vertical-align: middle;
+
+ &:hover {
+ opacity: 1;
+ }
+
+ .a-icon {
+ font-size: 2rem;
+ }
+ }
+
+ &__fip-debug-hint {
+ margin-bottom: 1.5rem;
+ }
+
+ &__fip-debug-key {
+ font-weight: 600;
+ white-space: nowrap;
+ padding-right: 1rem;
+ }
+
+ &__fip-debug-val {
+ font-family: monospace;
+ word-break: break-all;
+ }
+
+ &__fip-debug-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ margin-top: 1.5rem;
+
+ .a-icon {
+ font-size: 1.6rem;
+ }
+ }
+}
+
+@keyframes route-planner-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/content/country/austria/index.de.md b/content/country/austria/index.de.md
index e3964f407..8384cc6ac 100644
--- a/content/country/austria/index.de.md
+++ b/content/country/austria/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Österreich"
country: "austria"
params:
+ iso_code: AT
operators_without_fip:
- Achenseebahn
- '[CAT (City Airport Train) Wien](/operator/oebb#wien-flughafen-city-airport-train-cat "CAT")'
diff --git a/content/country/austria/index.en.md b/content/country/austria/index.en.md
index d89fa881f..ac649a14f 100644
--- a/content/country/austria/index.en.md
+++ b/content/country/austria/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Austria"
country: "austria"
params:
+ iso_code: AT
operators_without_fip:
- Achenseebahn
- '[CAT (City Airport Train) Vienna](/operator/oebb#vienna-airport-city-airport-train-cat "CAT")'
diff --git a/content/country/austria/index.fr.md b/content/country/austria/index.fr.md
index 3e2674efb..29488168f 100644
--- a/content/country/austria/index.fr.md
+++ b/content/country/austria/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Autriche"
country: "austria"
params:
+ iso_code: AT
operators_without_fip:
- Achenseebahn
- ’[CAT (City Airport Train) Vienne](/operator/oebb#aéroport-de-vienne--city-airport-train-cat "CAT")’
diff --git a/content/country/belgium/index.de.md b/content/country/belgium/index.de.md
index 4bd5e334c..49e343187 100644
--- a/content/country/belgium/index.de.md
+++ b/content/country/belgium/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Belgien"
country: "belgium"
params:
+ iso_code: BE
operators_without_fip:
- European Sleeper
- OUIGO
@@ -47,6 +48,14 @@ Von den Niederlanden aus können grenzüberschreitende Regionalzüge (dazu gehö
Mit dem Eurostar (ehemals Thalys) kann ebenfalls von den Niederlanden aus das Netz der SNCB erreicht werden. Hier wird ein spezielles FIP Ticket von Eurostar auf dem kompletten Abschnitt (auch innerhalb von Belgien) benötigt. ([siehe Eurostar](/operator/eurostar "Eurostar"))
+#### Dreiländerzug (Liège – Maastricht – Heerlen – Aachen)
+
+Seit Juni 2024 betreibt Arriva den Dreiländerzug zwischen Aachen (Deutschland), Heerlen (Niederlande), Maastricht (Niederlande) und Liège-Guillemins (Belgien). Auf dem Abschnitt zwischen Liège-Guillemins und Maastricht wird der Zug im Auftrag der SNCB (belgischer Abschnitt) und der NS (niederländischer Abschnitt bis Maastricht) betrieben. SNCB- und NS-Tickets, einschließlich FIP, bleiben auf diesem Abschnitt gültig.[^4]
+
+{{% highlight important %}}
+FIP ist zwischen Liège-Guillemins und Maastricht gültig. Jenseits von Maastricht in Richtung Heerlen und Aachen wird der Zug von Arriva betrieben und FIP ist auf diesem Abschnitt nicht gültig.
+{{% /highlight %}}
+
### Deutschland
#### Fernverkehr
@@ -88,3 +97,5 @@ Außerdem gibt es verschiedene `TER` Regionalzugverbindungen von Frankreich nach
[^2]: [DB Presse: ICE zum Flughafen Brüssel](https://www.deutschebahn.com/de/presse/pressestart_zentrales_uebersicht/Der-ICE-faehrt-zum-Flughafen-Bruessel-neue-Kooperation-von-DB-und-Brussels-Airlines-13703430)
[^3]: [FIP Guide Community - Feedback](https://discord.com/channels/1250522473188032512/1480609147828441108/1480609147828441108)
+
+[^4]: [Arriva Dreiländerzug](https://www.arriva.nl/over-je-reis/met-de-trein/de-drielandentrein/)
diff --git a/content/country/belgium/index.en.md b/content/country/belgium/index.en.md
index 3818b497a..b47f6c6e9 100644
--- a/content/country/belgium/index.en.md
+++ b/content/country/belgium/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Belgium"
country: "belgium"
params:
+ iso_code: BE
operators_without_fip:
- European Sleeper
- OUIGO
@@ -47,6 +48,14 @@ From the Netherlands, cross-border regional trains (including the `IC`) can be u
The Eurostar (formerly Thalys) can also be used from the Netherlands to reach the SNCB network. Here, a special FIP Ticket from Eurostar is required for the entire section, including within Belgium. ([see Eurostar](/operator/eurostar "Eurostar"))
+#### Three-Country Train (Liège – Maastricht – Heerlen – Aachen)
+
+Since June 2024, Arriva operates the Three-Country Train between Aachen (Germany), Heerlen (Netherlands), Maastricht (Netherlands), and Liège-Guillemins (Belgium). On the section between Liège-Guillemins and Maastricht, the train is operated on behalf of SNCB (Belgian section) and NS (Dutch section up to Maastricht). SNCB and NS tickets, including FIP, remain valid on this section.[^4]
+
+{{% highlight important %}}
+FIP is valid between Liège-Guillemins and Maastricht. Beyond Maastricht towards Heerlen and Aachen, the train is operated by Arriva and FIP is not valid on this section.
+{{% /highlight %}}
+
### Germany
#### Long-distance
@@ -88,3 +97,5 @@ There are also various `TER` regional train connections from France to Belgium t
[^2]: [DB Press: ICE to Brussels Airport](https://www.deutschebahn.com/de/presse/pressestart_zentrales_uebersicht/Der-ICE-faehrt-zum-Flughafen-Bruessel-neue-Kooperation-von-DB-und-Brussels-Airlines-13703430)
[^3]: [FIP Guide Community - Feedback](https://discord.com/channels/1250522473188032512/1480609147828441108/1480609147828441108)
+
+[^4]: [Arriva Three-Country Train](https://www.arriva.nl/over-je-reis/met-de-trein/de-drielandentrein/)
diff --git a/content/country/belgium/index.fr.md b/content/country/belgium/index.fr.md
index 372f96073..83816da72 100644
--- a/content/country/belgium/index.fr.md
+++ b/content/country/belgium/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Belgique"
country: "belgium"
params:
+ iso_code: BE
operators_without_fip:
- European Sleeper
- OUIGO
@@ -47,6 +48,14 @@ Depuis les Pays-Bas, il est possible d’utiliser les trains régionaux transfro
L’Eurostar (anciennement Thalys) peut également être utilisé depuis les Pays-Bas pour rejoindre le réseau SNCB. Dans ce cas, un Billet FIP spécial Eurostar est nécessaire pour l’ensemble du trajet, y compris en Belgique. ([voir Eurostar](/operator/eurostar "Eurostar"))
+#### Train des Trois Pays (Liège – Maastricht – Heerlen – Aix-la-Chapelle)
+
+Depuis juin 2024, Arriva exploite le Train des Trois Pays entre Aix-la-Chapelle (Allemagne), Heerlen (Pays-Bas), Maastricht (Pays-Bas) et Liège-Guillemins (Belgique). Sur la section entre Liège-Guillemins et Maastricht, le train est exploité pour le compte de la SNCB (section belge) et de la NS (section néerlandaise jusqu’à Maastricht). Les billets SNCB et NS, y compris les titres FIP, restent valables sur cette section.[^4]
+
+{{% highlight important %}}
+Le FIP est valable entre Liège-Guillemins et Maastricht. Au-delà de Maastricht en direction de Heerlen et Aix-la-Chapelle, le train est exploité par Arriva et le FIP n’est pas valable sur cette section.
+{{% /highlight %}}
+
### Allemagne
#### Grande vitesse
@@ -88,3 +97,5 @@ Par ailleurs, plusieurs liaisons régionales `TER` existent entre la France et l
[^2]: [DB Presse : ICE vers l’aéroport de Bruxelles](https://www.deutschebahn.com/de/presse/pressestart_zentrales_uebersicht/Der-ICE-faehrt-zum-Flughafen-Bruessel-neue-Kooperation-von-DB-und-Brussels-Airlines-13703430)
[^3]: [Communauté FIP Guide - Retour d’information](https://discord.com/channels/1250522473188032512/1480609147828441108/1480609147828441108)
+
+[^4]: [Arriva Train des Trois Pays](https://www.arriva.nl/over-je-reis/met-de-trein/de-drielandentrein/)
diff --git a/content/country/bulgaria/index.de.md b/content/country/bulgaria/index.de.md
index d7679d870..d28e68b30 100644
--- a/content/country/bulgaria/index.de.md
+++ b/content/country/bulgaria/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Bulgarien"
country: "bulgaria"
params:
+ iso_code: BG
operators_without_fip:
- Optima Express
---
diff --git a/content/country/bulgaria/index.en.md b/content/country/bulgaria/index.en.md
index e8cca3580..5e712d01c 100644
--- a/content/country/bulgaria/index.en.md
+++ b/content/country/bulgaria/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Bulgaria"
country: "bulgaria"
params:
+ iso_code: BG
operators_without_fip:
- Optima Express
---
diff --git a/content/country/bulgaria/index.fr.md b/content/country/bulgaria/index.fr.md
index dab58f35d..d5fbf91fa 100644
--- a/content/country/bulgaria/index.fr.md
+++ b/content/country/bulgaria/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Bulgarie"
country: "bulgaria"
params:
+ iso_code: BG
operators_without_fip:
- Optima Express
---
diff --git a/content/country/czechia/index.de.md b/content/country/czechia/index.de.md
index 162f373e9..082f7146e 100644
--- a/content/country/czechia/index.de.md
+++ b/content/country/czechia/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Tschechien"
country: "czechia"
params:
+ iso_code: CZ
operators_without_fip:
- ARRIVA vlaky s. r. o.
- AŽD Praha s.r.o.
diff --git a/content/country/czechia/index.en.md b/content/country/czechia/index.en.md
index 1b2f97144..6246177f7 100644
--- a/content/country/czechia/index.en.md
+++ b/content/country/czechia/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Czechia"
country: "czechia"
params:
+ iso_code: CZ
operators_without_fip:
- ARRIVA vlaky s. r. o.
- AŽD Praha s.r.o.
diff --git a/content/country/czechia/index.fr.md b/content/country/czechia/index.fr.md
index 04f8526d0..b1c6d8dda 100644
--- a/content/country/czechia/index.fr.md
+++ b/content/country/czechia/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Tchéquie"
country: "czechia"
params:
+ iso_code: CZ
operators_without_fip:
- ARRIVA vlaky s. r. o.
- AŽD Praha s.r.o.
diff --git a/content/country/denmark/index.de.md b/content/country/denmark/index.de.md
index b3737d735..ee1bd1118 100644
--- a/content/country/denmark/index.de.md
+++ b/content/country/denmark/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Dänemark"
country: "denmark"
params:
+ iso_code: DK
operators_without_fip:
- GoCollective (ehemals Arriva Danmark)
- Lokaltog
diff --git a/content/country/denmark/index.en.md b/content/country/denmark/index.en.md
index c795f5a24..bda52759c 100644
--- a/content/country/denmark/index.en.md
+++ b/content/country/denmark/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Denmark"
country: "denmark"
params:
+ iso_code: DK
operators_without_fip:
- GoCollective (ehemals Arriva Danmark)
- Lokaltog
diff --git a/content/country/denmark/index.fr.md b/content/country/denmark/index.fr.md
index 5513c5aa7..74f677d68 100644
--- a/content/country/denmark/index.fr.md
+++ b/content/country/denmark/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Danemark"
country: "denmark"
params:
+ iso_code: DK
operators_without_fip:
- GoCollective (anciennement Arriva Danmark)
- Lokaltog
diff --git a/content/country/denmark/transitous.yaml b/content/country/denmark/transitous.yaml
new file mode 100644
index 000000000..5c35b6fe6
--- /dev/null
+++ b/content/country/denmark/transitous.yaml
@@ -0,0 +1,7 @@
+- query: agencyId == "471" # agencyName: "GoCollective"
+- query: agencyId == "316" # agencyName: "Lokaltog A/S"
+- query: agencyId == "300" # agencyName: "Öresundståg"
+- query: agencyId == "281" && mode == "REGIONAL_RAIL" # agencyName: "Midttrafik" (line 92 Holstebro–Vemb run by Midtjyske Jernbaner but attributed to the Midttrafik PTA in the feed)
+- query: agencyId == "206" # agencyName: "NT" (Nordjyske Jernbaner)
+- query: agencyId == "74" # agencyName: "SJ"
+- query: agencyId == "451" # agencyName: "Snälltåget AB"
diff --git a/content/country/france/index.de.md b/content/country/france/index.de.md
index e33436df5..d215ea1cf 100644
--- a/content/country/france/index.de.md
+++ b/content/country/france/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Frankreich"
country: "france"
params:
+ iso_code: FR
operators_without_fip:
- CFC (Chemins de fer de la Corse / Eisenbahnen auf Korsika)
- '[Frecciarossa (Trenitalia)](/operator/fs/#internationale-frecciarossa-züge-nach-paris "Frecciarossa (Trenitalia)")'
diff --git a/content/country/france/index.en.md b/content/country/france/index.en.md
index a0aee3cd8..f58a425dc 100644
--- a/content/country/france/index.en.md
+++ b/content/country/france/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "France"
country: "france"
params:
+ iso_code: FR
operators_without_fip:
- CFC (Chemins de fer de la Corse / Railways in Corsica)
- '[Frecciarossa (Trenitalia)](/operator/fs/#international-frecciarossa-trains-to-paris "Frecciarossa (Trenitalia)")'
diff --git a/content/country/france/index.fr.md b/content/country/france/index.fr.md
index 6b58949ef..3cd5b20de 100644
--- a/content/country/france/index.fr.md
+++ b/content/country/france/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "France"
country: "france"
params:
+ iso_code: FR
operators_without_fip:
- CFC (Chemins de fer de la Corse / Chemins de fer corses)
- ’[Frecciarossa (Trenitalia)](/operator/fs/#trains-frecciarossa-internationaux-vers-paris "Frecciarossa (Trenitalia)")’
diff --git a/content/country/germany/index.de.md b/content/country/germany/index.de.md
index 041f67a40..a880b0faf 100644
--- a/content/country/germany/index.de.md
+++ b/content/country/germany/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Deutschland"
country: "germany"
params:
+ iso_code: DE
operators_without_fip:
- Abellio Rail Mitteldeutschland GmbH
- agilis – ag
diff --git a/content/country/germany/index.en.md b/content/country/germany/index.en.md
index 73668c8ce..937ee83b2 100644
--- a/content/country/germany/index.en.md
+++ b/content/country/germany/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Germany"
country: "germany"
params:
+ iso_code: DE
operators_without_fip:
- Abellio Rail Mitteldeutschland GmbH
- agilis – ag
diff --git a/content/country/germany/index.fr.md b/content/country/germany/index.fr.md
index 1a015854e..93330577b 100644
--- a/content/country/germany/index.fr.md
+++ b/content/country/germany/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Allemagne"
country: "germany"
params:
+ iso_code: DE
operators_without_fip:
- Abellio Rail Mitteldeutschland GmbH
- agilis – ag
diff --git a/content/country/germany/transitous.yaml b/content/country/germany/transitous.yaml
new file mode 100644
index 000000000..224420e3c
--- /dev/null
+++ b/content/country/germany/transitous.yaml
@@ -0,0 +1,65 @@
+- query: agencyId == "11961" # agencyName: "Abellio Rail Mitteldeutschland GmbH"
+- query: agencyId == "10838" || agencyId == "10940" # agencyName: "agilis" / "agilis-Schnellzug"
+- query: agencyId == "10833" # agencyName: "AKN Eisenbahn AG"
+- query: agencyId == "10836" # agencyName: "Albtal-Verkehrs-Gesellschaft"
+- query: agencyId == "10837" # agencyName: "alex - Die Länderbahn GmbH DLB"
+- query: agencyId == "IFF:ARRIVA" || agencyId == "ARR" # agencyName: "Arriva" (NL cross-border trains + Buses)
+- query: agencyId == "15251" # agencyName: "Arverio Baden-Württemberg GmbH"
+- query: agencyId == "15250" # agencyName: "Arverio Bayern GmbH"
+- query: agencyId == "10969" # agencyName: "Bayerische Regiobahn"
+- query: agencyId == "10971" # agencyName: "Bayerische Zugspitzbahn"
+- query: agencyId == "10847" || agencyId == "1249" # agencyName: "Bentheimer Eisenbahn"
+- query: agencyId == "10867" # agencyName: "Bodensee-Oberschwaben-Bahn"
+- query: agencyId == "10845" # agencyName: "Brohltalbahn"
+- query: agencyId == "10907" || agencyId == "608" # agencyName: "cantus Verkehrsgesellschaft"
+- query: agencyId == "10851" # agencyName: "City-Bahn Chemnitz"
+- query: agencyId == "10959" # agencyName: "Daadetalbahn"
+- query: agencyId == "10358" # agencyName: "Dessauer Verkehrs- und Eisenbahngesellschaft" (routeShortName: DWE)
+- query: agencyId == "8654" # agencyName: "Erfurter Bahn"
+- query: agencyId == "10966" # agencyName: "erixx"
+- query: agencyId == "2155" || agencyId == "11049" # agencyName: "eurobahn" / "Eurobahn"
+- query: agencyId == "es" # agencyName: "European Sleeper"
+- query: agencyId == "10861" # agencyName: "EVB ELBE-WESER GmbH"
+- query: agencyId == "13796" || agencyId == "FLIXTRAIN-eu" # agencyName: "FlixTrain-de" / "FlixTrain-eu"
+- query: agencyId == "10899" # agencyName: "Mainschleifenbahn"
+- query: agencyId == "IFF:GV" # agencyName: "GoVolta"
+- query: agencyId == "10927" || agencyId == "751" # agencyName: "Hanseatische Eisenbahn GmbH"
+- query: agencyId == "10875" || agencyId == "8875" # agencyName: "Harzer Schmalspurbahn" / "Harzer Schmalspurbahnen"
+- query: agencyId == "11046" # agencyName: "Hessische Landesbahn GmbH"
+- query: agencyId == "10848" # agencyName: "Kandertalbahn"
+- query: agencyId == "10862" # agencyName: "Kasbachtalbahn"
+- query: agencyId == "10385" # agencyName: "LEO Express"
+- query: agencyId == "10904" # agencyName: "Mecklenburgische Bäderbahn Molli"
+- query: agencyId == "10931" || agencyId == "606" # agencyName: "metronom" / "metronom Eisenbahngesellschaft mbH"
+- query: agencyId == "10853" || agencyId == "856" # agencyName: "Mitteldeutsche Regiobahn"
+- query: agencyId == "10946" # agencyName: "MittelrheinBahn (Trans Regio)"
+- query: agencyName == "National Express"
+- query: agencyId == "10912" # agencyName: "NEB Niederbarnimer Eisenbahn"
+- query: agencyId == "10905" # agencyName: "Norddeutsche Eisenbahn Gesellschaft"
+- query: agencyId == "10919" # agencyName: "Nordbahn Eisenbahngesellschaft"
+- query: agencyId == "12638" || agencyId == "605" # agencyName: "NordWestBahn"
+- query: agencyId == "10923" # agencyName: "oberpfalzbahn - Die Länderbahn GmbH DLB"
+- query: agencyId == "7882" || agencyId == "731" # agencyName: "Ostdeutsche Eisenbahn GmbH" / "ODEG Ostdeutsche Eisenbahn GmbH"
+- query: agencyId == "10937" # agencyName: "Pressnitztalbahn"
+- query: agencyId == "10894" # agencyName: "REGIOBAHN"
+- query: agencyId == "14173" # agencyName: "RheinRuhrBahn"
+- query: agencyId == "10858" # agencyName: "Rurtalbahn"
+- query: agencyId == "8931" # agencyName: "Saarbahn GmbH"
+- query: agencyId == "10924" # agencyName: "Sächsisch-Oberlausitzer Eisenbahngesellschaft"
+- query: agencyId == "2506" # agencyName: "S-Bahn Hannover (Transdev)"
+- query: agencyId == "351" || agencyId == "L7____" # agencyName: "SBB GmbH (Grenzverkehr)" / "SBB GmbH"
+- query: agencyId == "10918" # agencyName: "DB Fernverkehr (Codesharing)" (IC services operated by SVG on behalf of DB Fernverkehr)
+- query: agencyId == "12894" # agencyName: "Schwäbische Alb-Bahn"
+- query: agencyId == "10846" # agencyName: "SDG Sächsische Dampfeisenbahngesellschaft mbH"
+- query: agencyId == "10942" # agencyName: "Süd-Thüringen-Bahn"
+- query: agencyId == "10939" # agencyName: "Südwestdeutsche Verkehrs-AG" (SWEG operates under this brand)
+- query: agencyId == "10892" # agencyName: "trilex - Die Länderbahn GmbH DLB"
+- query: agencyId == "10890" # agencyName: "trilex-express - Die Länderbahn GmbH DLB"
+- query: agencyId == "13122" # agencyName: "TRI Train Rental GmbH"
+- query: agencyId == "10932" # agencyName: "VIAS GmbH"
+- query: agencyId == "10933" # agencyName: "VIAS Rail GmbH"
+- query: agencyId == "10951" || agencyId == "10952" # agencyName: "vlexx" / "vlexx1"
+- query: agencyId == "10382" # agencyName: "vogtlandbahn - Die Länderbahn GmbH DLB"
+- query: agencyId == "10964" # agencyName: "waldbahn - Die Länderbahn GmbH DLB"
+- query: agencyId == "02" || agencyId == "10383" # agencyName: "WESTbahn Management GmbH" / "WESTbahn"
+- query: agencyId == "13291" || agencyId == "810" # agencyName: "WestfalenBahn"
diff --git a/content/country/greece/index.de.md b/content/country/greece/index.de.md
index 03a1b826b..938e56ff7 100644
--- a/content/country/greece/index.de.md
+++ b/content/country/greece/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Griechenland"
country: "greece"
params:
+ iso_code: GR
operators_without_fip:
- STASY (Urban Rail Transport S.A.)
- THEMA S.A. – Thessaloniki Metro
diff --git a/content/country/greece/index.en.md b/content/country/greece/index.en.md
index 66b8c77a9..305147e4d 100644
--- a/content/country/greece/index.en.md
+++ b/content/country/greece/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Greece"
country: "greece"
params:
+ iso_code: GR
operators_without_fip:
- STASY (Urban Rail Transport S.A.)
- THEMA S.A. – Thessaloniki Metro
diff --git a/content/country/greece/index.fr.md b/content/country/greece/index.fr.md
index bfd619d1d..bb13e7676 100644
--- a/content/country/greece/index.fr.md
+++ b/content/country/greece/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Grèce"
country: "greece"
params:
+ iso_code: GR
operators_without_fip:
- STASY (Urban Rail Transport S.A.)
- THEMA S.A. – Thessaloniki Metro
diff --git a/content/country/ireland/index.de.md b/content/country/ireland/index.de.md
index 37e1037d8..223bc7e4e 100644
--- a/content/country/ireland/index.de.md
+++ b/content/country/ireland/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Irland"
country: "ireland"
params:
+ iso_code: IE
operators_without_fip:
- Transdev (Luas - Straßenbahn Dublin)
---
diff --git a/content/country/ireland/index.en.md b/content/country/ireland/index.en.md
index 599690a57..8c51089c1 100644
--- a/content/country/ireland/index.en.md
+++ b/content/country/ireland/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Ireland"
country: "ireland"
params:
+ iso_code: IE
operators_without_fip:
- Transdev (Luas - Dublin Tram)
---
diff --git a/content/country/ireland/index.fr.md b/content/country/ireland/index.fr.md
index 117c49ffc..6701116d0 100644
--- a/content/country/ireland/index.fr.md
+++ b/content/country/ireland/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Irlande"
country: "ireland"
params:
+ iso_code: IE
operators_without_fip:
- Transdev (Luas - Tramway de Dublin)
---
diff --git a/content/country/ireland/transitous.yaml b/content/country/ireland/transitous.yaml
new file mode 100644
index 000000000..34b000064
--- /dev/null
+++ b/content/country/ireland/transitous.yaml
@@ -0,0 +1 @@
+- query: agencyId == "10000" # agencyName: "LUAS"
diff --git a/content/country/italy/index.de.md b/content/country/italy/index.de.md
index 5f21b4ddb..d3f4a09f3 100644
--- a/content/country/italy/index.de.md
+++ b/content/country/italy/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Italien"
country: "italy"
params:
+ iso_code: IT
operators_without_fip:
- Azienda Regionale Sarda Trasporti
- Circumflegrea
diff --git a/content/country/italy/index.en.md b/content/country/italy/index.en.md
index ea61fb3e6..2a14383f3 100644
--- a/content/country/italy/index.en.md
+++ b/content/country/italy/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Italy"
country: "italy"
params:
+ iso_code: IT
operators_without_fip:
- Azienda Regionale Sarda Trasporti
- Circumflegrea
diff --git a/content/country/italy/index.fr.md b/content/country/italy/index.fr.md
index b42c7be20..960392443 100644
--- a/content/country/italy/index.fr.md
+++ b/content/country/italy/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Italie"
country: "italy"
params:
+ iso_code: IT
operators_without_fip:
- Azienda Regionale Sarda Trasporti
- Circumflegrea
diff --git a/content/country/latvia/index.de.md b/content/country/latvia/index.de.md
index a73da1920..5ac0965eb 100644
--- a/content/country/latvia/index.de.md
+++ b/content/country/latvia/index.de.md
@@ -3,8 +3,9 @@ draft: false
title: "Lettland"
country: "latvia"
params:
+ iso_code: LV
operators_without_fip:
- - "Latvijas dzelzceļš (LDz)"
+ - Latvijas dzelzceļš (LDz)
---
## FIP Nutzung
diff --git a/content/country/latvia/index.en.md b/content/country/latvia/index.en.md
index 9bb1e19be..b4df14c3c 100644
--- a/content/country/latvia/index.en.md
+++ b/content/country/latvia/index.en.md
@@ -3,8 +3,9 @@ draft: false
title: "Latvia"
country: "latvia"
params:
+ iso_code: LV
operators_without_fip:
- - "Latvijas dzelzceļš (LDz)"
+ - Latvijas dzelzceļš (LDz)
---
## FIP Information
diff --git a/content/country/latvia/index.fr.md b/content/country/latvia/index.fr.md
index b08bd0738..89d465268 100644
--- a/content/country/latvia/index.fr.md
+++ b/content/country/latvia/index.fr.md
@@ -3,8 +3,9 @@ draft: false
title: "Lettonie"
country: "latvia"
params:
+ iso_code: LV
operators_without_fip:
- - "Latvijas dzelzceļš (LDz)"
+ - Latvijas dzelzceļš (LDz)
---
## Informations FIP
diff --git a/content/country/lithuania/index.de.md b/content/country/lithuania/index.de.md
index ed5af6889..ffde83cc4 100644
--- a/content/country/lithuania/index.de.md
+++ b/content/country/lithuania/index.de.md
@@ -3,8 +3,9 @@ draft: false
title: "Litauen"
country: "lithuania"
params:
+ iso_code: LT
operators_without_fip:
- - "Aukštaitijos siaurasis geležinkelis (Museumsbahn)"
+ - Aukštaitijos siaurasis geležinkelis (Museumsbahn)
---
## FIP Nutzung
diff --git a/content/country/lithuania/index.en.md b/content/country/lithuania/index.en.md
index 3a82574b3..998a2e544 100644
--- a/content/country/lithuania/index.en.md
+++ b/content/country/lithuania/index.en.md
@@ -3,8 +3,9 @@ draft: false
title: "Lithuania"
country: "lithuania"
params:
+ iso_code: LT
operators_without_fip:
- - "Aukštaitijos siaurasis geležinkelis (heritage railway)"
+ - Aukštaitijos siaurasis geležinkelis (heritage railway)
---
## FIP Information
diff --git a/content/country/lithuania/index.fr.md b/content/country/lithuania/index.fr.md
index 0bc3f00df..f8453cce2 100644
--- a/content/country/lithuania/index.fr.md
+++ b/content/country/lithuania/index.fr.md
@@ -3,8 +3,9 @@ draft: false
title: "Lituanie"
country: "lithuania"
params:
+ iso_code: LT
operators_without_fip:
- - "Aukštaitijos siaurasis geležinkelis (chemin de fer musée)"
+ - Aukštaitijos siaurasis geležinkelis (chemin de fer musée)
---
## Informations FIP
diff --git a/content/country/netherlands/index.de.md b/content/country/netherlands/index.de.md
index bc86a8298..b4d790cdc 100644
--- a/content/country/netherlands/index.de.md
+++ b/content/country/netherlands/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Niederlande"
country: "netherlands"
params:
+ iso_code: NL
operators_without_fip:
- Arriva Nederland
- Breng
@@ -20,7 +21,7 @@ In den Niederlanden können FIP 50 Tickets und FIP Freifahrtscheine auf einem gr
Zusätzlich verkehren internationale [Eurostar](/operator/eurostar "Eurostar") Züge, welche vergünstigt mit speziellen FIP Globalpreisen genutzt werden können. Die Züge sind in der Verbindungsauskunft als Zugkategorie `EST` ausgewiesen.
-Andere Betreiber wie Arriva Nederland oder die grenzüberschreitenden `RE` Züge Arnhem - Düsseldorf, Venlo - Hamm und Maastricht - Aachen sind dagegen leider nicht mit FIP nutzbar. Auch sämtliche Busse (außer im Schienenersatzverkehr), Straßenbahnen und U-Bahnen sind nicht enthalten.
+Andere Betreiber wie Arriva Nederland oder die grenzüberschreitenden `RE` Züge Arnhem - Düsseldorf und Venlo - Hamm sind dagegen leider nicht mit FIP nutzbar. Beim Dreiländerzug (Maastricht – Heerlen – Aachen) ist FIP nur auf dem Abschnitt zwischen Maastricht und Liège-Guillemins gültig (betrieben im Auftrag von NS und SNCB), nicht jedoch zwischen Maastricht und Aachen. Auch sämtliche Busse (außer im Schienenersatzverkehr), Straßenbahnen und U-Bahnen sind nicht enthalten.
{{< identify-operator sources="ns-website,db-website" />}}
@@ -65,6 +66,14 @@ Von Belgien aus können grenzüberschreitende Regionalzüge (dazu gehört hier a
Mit dem Eurostar (ehemals Thalys) kann ebenfalls von den Niederlanden aus das Netz der SNCB erreicht werden. Hier wird ein spezielles FIP Ticket von Eurostar auf dem kompletten Abschnitt (auch innerhalb der Niederlande) benötigt. ([siehe Eurostar](/operator/eurostar "Eurostar"))
+#### Dreiländerzug (Liège – Maastricht – Heerlen – Aachen)
+
+Seit Juni 2024 betreibt Arriva den Dreiländerzug zwischen Aachen (Deutschland), Heerlen (Niederlande), Maastricht (Niederlande) und Liège-Guillemins (Belgien). Auf dem Abschnitt zwischen Maastricht und Liège-Guillemins wird der Zug im Auftrag der NS (niederländischer Abschnitt) und der SNCB (belgischer Abschnitt) betrieben. NS- und SNCB-Tickets, einschließlich FIP, bleiben auf diesem Abschnitt gültig.
+
+{{% highlight important %}}
+FIP ist zwischen Maastricht und Liège-Guillemins gültig. Zwischen Maastricht und Aachen (über Heerlen) wird der Zug von Arriva betrieben und FIP ist nicht gültig.
+{{% /highlight %}}
+
### Vereinigtes Königreich
Von London St. Pancras verkehren durchgängige [Eurostar](/operator/eurostar "Eurostar") Züge nach Amsterdam und Rotterdam. Die Eurostar Züge sind immer reservierungspflichtig und es müssen FIP Globalpreis-Tickets erworben werden.
diff --git a/content/country/netherlands/index.en.md b/content/country/netherlands/index.en.md
index d2c057ad6..9db744678 100644
--- a/content/country/netherlands/index.en.md
+++ b/content/country/netherlands/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Netherlands"
country: "netherlands"
params:
+ iso_code: NL
operators_without_fip:
- Arriva Nederland
- Breng
@@ -20,7 +21,7 @@ In the Netherlands, FIP 50 Tickets and FIP Coupons can be used on a large part o
Additionally, international [Eurostar](/operator/eurostar "Eurostar") trains operate, which can be used at a discount with special FIP Global Fares. These trains are listed as train category `EST` in journey planners.
-Other operators such as Arriva Nederland or the cross-border `RE` trains Arnhem - Düsseldorf, Venlo - Hamm, and Maastricht - Aachen unfortunately do not accept FIP. All buses (except rail replacement services), trams, and metros are also excluded.
+Other operators such as Arriva Nederland or the cross-border `RE` trains Arnhem - Düsseldorf and Venlo - Hamm unfortunately do not accept FIP. On the Three-Country Train (Maastricht – Heerlen – Aachen), FIP is only valid on the section between Maastricht and Liège-Guillemins (operated on behalf of NS and SNCB), but not between Maastricht and Aachen. All buses (except rail replacement services), trams, and metros are also excluded.
{{< identify-operator sources="ns-website,db-website" />}}
@@ -65,6 +66,14 @@ From Belgium, cross-border regional trains (including the `IC` here) can be used
With the Eurostar (formerly Thalys), you can also reach the SNCB network from the Netherlands. Here, a special FIP Ticket from Eurostar is required for the entire route (including within the Netherlands). ([see Eurostar](/operator/eurostar "Eurostar"))
+#### Three-Country Train (Liège – Maastricht – Heerlen – Aachen)
+
+Since June 2024, Arriva operates the Three-Country Train between Aachen (Germany), Heerlen (Netherlands), Maastricht (Netherlands), and Liège-Guillemins (Belgium). On the section between Maastricht and Liège-Guillemins, the train is operated on behalf of NS (Dutch section) and SNCB (Belgian section). NS and SNCB tickets, including FIP, remain valid on this section.
+
+{{% highlight important %}}
+FIP is valid between Maastricht and Liège-Guillemins. Between Maastricht and Aachen (via Heerlen), the train is operated by Arriva and FIP is not valid.
+{{% /highlight %}}
+
### United Kingdom
Direct [Eurostar](/operator/eurostar "Eurostar") trains run from London St. Pancras to Amsterdam and Rotterdam. Reservations are always required for Eurostar trains, and FIP Global Fare tickets must be purchased.
diff --git a/content/country/netherlands/index.fr.md b/content/country/netherlands/index.fr.md
index 4663688ff..5fa7b824d 100644
--- a/content/country/netherlands/index.fr.md
+++ b/content/country/netherlands/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Pays-Bas"
country: "netherlands"
params:
+ iso_code: NL
operators_without_fip:
- Arriva Nederland
- Breng
@@ -20,7 +21,7 @@ Aux Pays-Bas, les Billets FIP 50 et les Coupons FIP peuvent être utilisés sur
Des trains internationaux [Eurostar](/operator/eurostar "Eurostar") circulent également, accessibles avec des billets spéciaux FIP à tarif global. Ces trains apparaissent sous la catégorie `EST` dans les horaires.
-D’autres opérateurs, comme Arriva Nederland, ou les trains régionaux transfrontaliers `RE` (Arnhem – Düsseldorf, Venlo – Hamm, Maastricht – Aix-la-Chapelle) n’acceptent pas les Billets FIP. Tous les bus (sauf remplacement rail), trams et métros sont également exclus.
+D’autres opérateurs, comme Arriva Nederland, ou les trains régionaux transfrontaliers `RE` (Arnhem – Düsseldorf, Venlo – Hamm) n’acceptent pas les Billets FIP. Sur le Train des Trois Pays (Maastricht – Heerlen – Aix-la-Chapelle), le FIP est uniquement valable sur la section entre Maastricht et Liège-Guillemins (exploitée pour le compte de la NS et de la SNCB), mais pas entre Maastricht et Aix-la-Chapelle. Tous les bus (sauf remplacement rail), trams et métros sont également exclus.
{{< identify-operator sources="ns-website,db-website" />}}
@@ -65,6 +66,14 @@ Depuis la Belgique, les trains régionaux transfrontaliers (y compris les `IC`)
L’Eurostar (anciennement Thalys) permet aussi de rejoindre la Belgique depuis les Pays-Bas. Un Billet FIP spécial Eurostar est alors requis pour tout le trajet, y compris la partie néerlandaise. ([voir Eurostar](/operator/eurostar "Eurostar"))
+#### Train des Trois Pays (Liège – Maastricht – Heerlen – Aix-la-Chapelle)
+
+Depuis juin 2024, Arriva exploite le Train des Trois Pays entre Aix-la-Chapelle (Allemagne), Heerlen (Pays-Bas), Maastricht (Pays-Bas) et Liège-Guillemins (Belgique). Sur la section entre Maastricht et Liège-Guillemins, le train est exploité pour le compte de la NS (section néerlandaise) et de la SNCB (section belge). Les billets NS et SNCB, y compris les titres FIP, restent valables sur cette section.
+
+{{% highlight important %}}
+Le FIP est valable entre Maastricht et Liège-Guillemins. Entre Maastricht et Aix-la-Chapelle (via Heerlen), le train est exploité par Arriva et le FIP n'est pas valable.
+{{% /highlight %}}
+
### Royaume-Uni
Depuis London St. Pancras, des trains [Eurostar](/operator/eurostar "Eurostar") directs relient Amsterdam et Rotterdam. Les trains Eurostar sont toujours soumis à réservation et des billets au tarif global FIP doivent être achetés.
diff --git a/content/country/norway/index.de.md b/content/country/norway/index.de.md
index cc1aa832d..457bdeb31 100644
--- a/content/country/norway/index.de.md
+++ b/content/country/norway/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Norwegen"
country: "norway"
params:
+ iso_code: NO
operators_without_fip:
- Arctic Train
- Flåmbahn
diff --git a/content/country/norway/index.en.md b/content/country/norway/index.en.md
index b3bca60d7..168314fd4 100644
--- a/content/country/norway/index.en.md
+++ b/content/country/norway/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Norway"
country: "norway"
params:
+ iso_code: NO
operators_without_fip:
- Arctic Train
- Flåm Railway
diff --git a/content/country/norway/index.fr.md b/content/country/norway/index.fr.md
index 524115657..72adfed10 100644
--- a/content/country/norway/index.fr.md
+++ b/content/country/norway/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Norvège"
country: "norway"
params:
+ iso_code: NO
operators_without_fip:
- Arctic Train
- Chemin de fer de Flåm
diff --git a/content/country/poland/index.de.md b/content/country/poland/index.de.md
index 88217bb57..4fc361e5d 100644
--- a/content/country/poland/index.de.md
+++ b/content/country/poland/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: Polen
country: "poland"
params:
+ iso_code: PL
operators_without_fip:
- Arriva
- Leo Express
diff --git a/content/country/poland/index.en.md b/content/country/poland/index.en.md
index ea5b4745b..fb409d64e 100644
--- a/content/country/poland/index.en.md
+++ b/content/country/poland/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Poland"
country: "poland"
params:
+ iso_code: PL
operators_without_fip:
- Arriva
- Leo Express
diff --git a/content/country/poland/index.fr.md b/content/country/poland/index.fr.md
index 0ae4af525..f6442d1dc 100644
--- a/content/country/poland/index.fr.md
+++ b/content/country/poland/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Pologne"
country: "poland"
params:
+ iso_code: PL
operators_without_fip:
- Arriva
- Leo Express
diff --git a/content/country/portugal/index.de.md b/content/country/portugal/index.de.md
index 3e3455d74..e40716dfa 100644
--- a/content/country/portugal/index.de.md
+++ b/content/country/portugal/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Portugal"
country: "portugal"
params:
+ iso_code: PT
operators_without_fip:
- Fertagus
---
diff --git a/content/country/portugal/index.en.md b/content/country/portugal/index.en.md
index cdbd6a04a..518c53d3c 100644
--- a/content/country/portugal/index.en.md
+++ b/content/country/portugal/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Portugal"
country: "portugal"
params:
+ iso_code: PT
operators_without_fip:
- Fertagus
---
diff --git a/content/country/portugal/index.fr.md b/content/country/portugal/index.fr.md
index 44fd2361c..0c6305dd2 100644
--- a/content/country/portugal/index.fr.md
+++ b/content/country/portugal/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Portugal"
country: "portugal"
params:
+ iso_code: PT
operators_without_fip:
- Fertagus
---
diff --git a/content/country/portugal/transitous.yaml b/content/country/portugal/transitous.yaml
new file mode 100644
index 000000000..12aadbb2d
--- /dev/null
+++ b/content/country/portugal/transitous.yaml
@@ -0,0 +1 @@
+- query: agencyId == "15" # agencyName: "Fertagus"
diff --git a/content/country/romania/index.de.md b/content/country/romania/index.de.md
index f5a33f026..97ab52805 100644
--- a/content/country/romania/index.de.md
+++ b/content/country/romania/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Rumänien"
country: "romania"
params:
+ iso_code: RO
operators_without_fip:
- Astra Trains Carpatic (ATC)
- IRC (InterRegional Călători)
diff --git a/content/country/romania/index.en.md b/content/country/romania/index.en.md
index c513c5fa1..35bd9a2c7 100644
--- a/content/country/romania/index.en.md
+++ b/content/country/romania/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Romania"
country: "romania"
params:
+ iso_code: RO
operators_without_fip:
- Astra Trains Carpatic (ATC)
- IRC (InterRegional Călători)
diff --git a/content/country/romania/index.fr.md b/content/country/romania/index.fr.md
index 14662d1a4..d71b245d9 100644
--- a/content/country/romania/index.fr.md
+++ b/content/country/romania/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Roumanie"
country: "romania"
params:
+ iso_code: RO
operators_without_fip:
- Astra Trains Carpatic (ATC)
- IRC (InterRegional Călători)
diff --git a/content/country/serbia/index.de.md b/content/country/serbia/index.de.md
index 3749872bf..d0921cca4 100644
--- a/content/country/serbia/index.de.md
+++ b/content/country/serbia/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Serbien"
country: "serbia"
params:
+ iso_code: RS
operators_without_fip:
- Optima Express
---
diff --git a/content/country/serbia/index.en.md b/content/country/serbia/index.en.md
index e848f0651..be638455c 100644
--- a/content/country/serbia/index.en.md
+++ b/content/country/serbia/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Serbia"
country: "serbia"
params:
+ iso_code: RS
operators_without_fip:
- Optima Express
---
diff --git a/content/country/serbia/index.fr.md b/content/country/serbia/index.fr.md
index dfb10ea75..20cec23d2 100644
--- a/content/country/serbia/index.fr.md
+++ b/content/country/serbia/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Serbie"
country: "serbia"
params:
+ iso_code: RS
operators_without_fip:
- Optima Express
---
diff --git a/content/country/slovakia/index.de.md b/content/country/slovakia/index.de.md
index bf3a045c3..ce1e513e0 100644
--- a/content/country/slovakia/index.de.md
+++ b/content/country/slovakia/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Slowakei"
country: "slovakia"
params:
+ iso_code: SK
operators_without_fip:
- RegioJet
- Leo Express
diff --git a/content/country/slovakia/index.en.md b/content/country/slovakia/index.en.md
index ceebc658b..cb7232a78 100644
--- a/content/country/slovakia/index.en.md
+++ b/content/country/slovakia/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Slovakia"
country: "slovakia"
params:
+ iso_code: SK
operators_without_fip:
- RegioJet
- Leo Express
diff --git a/content/country/slovakia/index.fr.md b/content/country/slovakia/index.fr.md
index 8925dc2a1..119bb13de 100644
--- a/content/country/slovakia/index.fr.md
+++ b/content/country/slovakia/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Slovaquie"
country: "slovakia"
params:
+ iso_code: SK
operators_without_fip:
- RegioJet
- Leo Express
diff --git a/content/country/slovenia/index.de.md b/content/country/slovenia/index.de.md
index e5b72c623..1cc105679 100644
--- a/content/country/slovenia/index.de.md
+++ b/content/country/slovenia/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Slowenien"
country: "slovenia"
params:
+ iso_code: SI
operators_without_fip:
- Optima Express
---
diff --git a/content/country/slovenia/index.en.md b/content/country/slovenia/index.en.md
index 0c095e6f5..cb507d64e 100644
--- a/content/country/slovenia/index.en.md
+++ b/content/country/slovenia/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Slovenia"
country: "slovenia"
params:
+ iso_code: SI
operators_without_fip:
- Optima Express
---
diff --git a/content/country/slovenia/index.fr.md b/content/country/slovenia/index.fr.md
index 42ccdb018..39936d934 100644
--- a/content/country/slovenia/index.fr.md
+++ b/content/country/slovenia/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Slovénie"
country: "slovenia"
params:
+ iso_code: SI
operators_without_fip:
- Optima Express
---
diff --git a/content/country/spain/index.de.md b/content/country/spain/index.de.md
index 460778168..8aea43e63 100644
--- a/content/country/spain/index.de.md
+++ b/content/country/spain/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Spanien"
country: "spain"
params:
+ iso_code: ES
operators_without_fip:
- Iryo
- '[OUIGO](/operator/sncf#Fernverkehr "OUIGO")'
diff --git a/content/country/spain/index.en.md b/content/country/spain/index.en.md
index 81afa741e..0d851b678 100644
--- a/content/country/spain/index.en.md
+++ b/content/country/spain/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Spain"
country: "spain"
params:
+ iso_code: ES
operators_without_fip:
- Iryo
- OUIGO
diff --git a/content/country/spain/index.fr.md b/content/country/spain/index.fr.md
index 252ede131..c891c9519 100644
--- a/content/country/spain/index.fr.md
+++ b/content/country/spain/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Espagne"
country: "spain"
params:
+ iso_code: ES
operators_without_fip:
- Iryo
- OUIGO
diff --git a/content/country/switzerland/index.de.md b/content/country/switzerland/index.de.md
index eb23e1175..467ec4838 100644
--- a/content/country/switzerland/index.de.md
+++ b/content/country/switzerland/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Schweiz"
country: "switzerland"
params:
+ iso_code: CH
operators_without_fip:
- European Sleeper
- Heimwehfluhbahn (Interlaken – Heimwehfluh)
diff --git a/content/country/switzerland/index.en.md b/content/country/switzerland/index.en.md
index af23b8528..eee685140 100644
--- a/content/country/switzerland/index.en.md
+++ b/content/country/switzerland/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "Switzerland"
country: "switzerland"
params:
+ iso_code: CH
operators_without_fip:
- European Sleeper
- Heimwehfluhbahn (Interlaken – Heimwehfluh)
diff --git a/content/country/switzerland/index.fr.md b/content/country/switzerland/index.fr.md
index 90c878022..d2f4e505f 100644
--- a/content/country/switzerland/index.fr.md
+++ b/content/country/switzerland/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Suisse"
country: "switzerland"
params:
+ iso_code: CH
operators_without_fip:
- European Sleeper
- Heimwehfluhbahn (Interlaken – Heimwehfluh)
diff --git a/content/country/united-kingdom/index.de.md b/content/country/united-kingdom/index.de.md
index bc20d208e..357c49fdf 100644
--- a/content/country/united-kingdom/index.de.md
+++ b/content/country/united-kingdom/index.de.md
@@ -3,6 +3,7 @@ draft: false
title: "Vereinigtes Königreich"
country: "united-kingdom"
params:
+ iso_code: GB
operators_without_fip:
- Blackpool Tramway
- Caledonian MacBrayne Ferries
diff --git a/content/country/united-kingdom/index.en.md b/content/country/united-kingdom/index.en.md
index be153f7c2..09d5bddde 100644
--- a/content/country/united-kingdom/index.en.md
+++ b/content/country/united-kingdom/index.en.md
@@ -3,6 +3,7 @@ draft: false
title: "United Kingdom"
country: "united-kingdom"
params:
+ iso_code: GB
operators_without_fip:
- Blackpool Tramway
- Caledonian MacBrayne Ferries
diff --git a/content/country/united-kingdom/index.fr.md b/content/country/united-kingdom/index.fr.md
index 0f577ca3a..9e4fc6dcf 100644
--- a/content/country/united-kingdom/index.fr.md
+++ b/content/country/united-kingdom/index.fr.md
@@ -3,6 +3,7 @@ draft: false
title: "Royaume-Uni"
country: "united-kingdom"
params:
+ iso_code: GB
operators_without_fip:
- Blackpool Tramway
- Caledonian MacBrayne Ferries
diff --git a/content/general/_index.de.md b/content/general/_index.de.md
index 0781ba536..af8df55cc 100644
--- a/content/general/_index.de.md
+++ b/content/general/_index.de.md
@@ -8,3 +8,4 @@ Hier findest du nützliche, allgemeine Informationen zur Reise mit FIP.
- [Übergreifende Infos](general/generalinformation)
- [FIP Beantragung](general/fip-validity)
- [FAQ](general/faq)
+- [Streckenplaner](general/route-planner)
diff --git a/content/general/_index.en.md b/content/general/_index.en.md
index 353da579d..fba0454eb 100644
--- a/content/general/_index.en.md
+++ b/content/general/_index.en.md
@@ -8,3 +8,4 @@ On this pages you can find useful, general information regarding FIP.
- [General Information](general/generalinformation)
- [FIP Application](general/fip-validity)
- [FAQ](general/faq)
+- [Route Planner](general/route-planner)
diff --git a/content/general/_index.fr.md b/content/general/_index.fr.md
index 75c7e20b2..67643a793 100644
--- a/content/general/_index.fr.md
+++ b/content/general/_index.fr.md
@@ -8,3 +8,4 @@ Sur ces pages, vous trouverez des informations générales et utiles concernant
- [Informations générales](general/generalinformation)
- [Demande FIP](general/fip-validity)
- [FAQ](general/faq)
+- [Planificateur d'itinéraire](general/route-planner)
diff --git a/content/general/route-planner/index.de.md b/content/general/route-planner/index.de.md
new file mode 100644
index 000000000..689cf2a98
--- /dev/null
+++ b/content/general/route-planner/index.de.md
@@ -0,0 +1,4 @@
+---
+draft: false
+title: "Streckenplaner"
+---
diff --git a/content/general/route-planner/index.en.md b/content/general/route-planner/index.en.md
new file mode 100644
index 000000000..eb7ff77d6
--- /dev/null
+++ b/content/general/route-planner/index.en.md
@@ -0,0 +1,4 @@
+---
+draft: false
+title: "Route Planner"
+---
diff --git a/content/general/route-planner/index.fr.md b/content/general/route-planner/index.fr.md
new file mode 100644
index 000000000..79b3d07b0
--- /dev/null
+++ b/content/general/route-planner/index.fr.md
@@ -0,0 +1,4 @@
+---
+draft: false
+title: "Planificateur d'itinéraire"
+---
diff --git a/content/operator/bls/transitous.yaml b/content/operator/bls/transitous.yaml
new file mode 100644
index 000000000..f06a99e40
--- /dev/null
+++ b/content/operator/bls/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "BLS AG"
+- query: agencyName == "BLS"
diff --git a/content/operator/cd/transitous.yaml b/content/operator/cd/transitous.yaml
new file mode 100644
index 000000000..971bb8c36
--- /dev/null
+++ b/content/operator/cd/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "České dráhy"
+- query: agencyName == "ČD"
diff --git a/content/operator/cie/transitous.yaml b/content/operator/cie/transitous.yaml
new file mode 100644
index 000000000..902fe5e63
--- /dev/null
+++ b/content/operator/cie/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "Irish Rail"
+- query: agencyName == "Iarnród Éireann"
diff --git a/content/operator/cp/transitous.yaml b/content/operator/cp/transitous.yaml
new file mode 100644
index 000000000..fb2f8d3b7
--- /dev/null
+++ b/content/operator/cp/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "CP - Comboios de Portugal"
+- query: agencyName == "Comboios de Portugal"
diff --git a/content/operator/db/transitous.yaml b/content/operator/db/transitous.yaml
new file mode 100644
index 000000000..5602d12d9
--- /dev/null
+++ b/content/operator/db/transitous.yaml
@@ -0,0 +1,22 @@
+- query: (agencyName == "DB Fernverkehr AG" && displayName.startsWith("ICE") || agencyId == "IFF:NS_INT" && routeShortName == "ICE") && fromCountry == "DE" && toCountry == "DE"
+ category: ice
+ reservation_required: false
+ priority: 1
+- query: agencyName == "DB Fernverkehr AG" && displayName.startsWith("ICE")
+ category: ice
+- query: agencyName == "DB Fernverkehr AG" && displayName.startsWith("TGV")
+ category: tgv
+- query: agencyName == "DB Fernverkehr AG" && (displayName.startsWith("RJ") || displayName.startsWith("RJX"))
+ category: rj
+- query: agencyName == "DB Fernverkehr AG" && displayName.startsWith("ECE")
+ category: ece
+- query: agencyName == "DB Fernverkehr AG" && displayName.startsWith("EC")
+ category: ec
+- query: (agencyName == "DB Fernverkehr AG" || agencyId == "10918") && displayName.startsWith("IC")
+ category: ic
+- query: agencyName.startsWith("DB Regio") && routeShortName.startsWith("RE") && fromCountry == "DE" && toCountry == "DE"
+ category: re
+- query: agencyName.startsWith("DB Regio") && routeShortName.startsWith("RB") && fromCountry == "DE" && toCountry == "DE"
+ category: rb
+- query: agencyName.startsWith("DB Regio") && mode == "SUBURBAN" && fromCountry == "DE" && toCountry == "DE"
+ category: s
diff --git a/content/operator/eurostar/transitous.yaml b/content/operator/eurostar/transitous.yaml
new file mode 100644
index 000000000..8ea3118a3
--- /dev/null
+++ b/content/operator/eurostar/transitous.yaml
@@ -0,0 +1,12 @@
+- query: agencyId == "EUROSTAR_CHANNEL"
+ category: eurostar-blue
+ message:
+ en: "Global fare applies. Please check that this is not a Eurostar Snow service, as these are not covered by FIP."
+ de: "Globalpreis gilt. Bitte prüfe, dass es sich nicht um einen Eurostar Snow Service handelt, da diese nicht von FIP abgedeckt sind."
+ fr: "Le tarif global s'applique. Veuillez vérifier qu'il ne s'agit pas d'un service Eurostar Snow, car ceux-ci ne sont pas couverts par le FIP."
+- query: agencyId == "EUROSTAR_CONTINENTAL"
+ category: eurostar-red
+- query: agencyName == "NS Int" && routeShortName == "Eurostar"
+ category: eurostar-red
+- query: agencyName == "SNCF" && routeShortName == "THA"
+ category: eurostar-red
diff --git a/content/operator/gysev/transitous.yaml b/content/operator/gysev/transitous.yaml
new file mode 100644
index 000000000..23cd5deb7
--- /dev/null
+++ b/content/operator/gysev/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "GYSEV"
+- query: agencyName == "Raaberbahn"
diff --git a/content/operator/ht/transitous.yaml b/content/operator/ht/transitous.yaml
new file mode 100644
index 000000000..2120b0447
--- /dev/null
+++ b/content/operator/ht/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "HŽ Putnički prijevoz"
+- query: agencyName == "HŽ"
diff --git a/content/operator/nir/transitous.yaml b/content/operator/nir/transitous.yaml
new file mode 100644
index 000000000..4c681f457
--- /dev/null
+++ b/content/operator/nir/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "Translink NI Railways"
+- query: agencyName == "Northern Ireland Railways"
diff --git a/content/operator/ns/index.de.md b/content/operator/ns/index.de.md
index a4ae074ad..fdce27c8c 100644
--- a/content/operator/ns/index.de.md
+++ b/content/operator/ns/index.de.md
@@ -148,7 +148,9 @@ Anders als in anderen Ländern keine wirklichen Fernzüge, sondern eher schnelle
Regionalzüge mit mehr Halten als beim Intercity, aber trotzdem nur an wichtigeren Stationen.
{{% highlight confusion %}}
-Die Züge der Kategorie Sneltrein / Regional-Express `RE`, unter anderem die Verbindungen Venlo – Hamm (Deutschland), Maastricht – Aachen (Deutschland) und Arnhem – Düsseldorf (Deutschland) sowie andere RE-Verbindungen werden nicht von der NS betrieben und sind mit FIP nicht nutzbar.
+Die Züge der Kategorie Sneltrein / Regional-Express `RE`, unter anderem die Verbindungen Venlo – Hamm (Deutschland) und Arnhem – Düsseldorf (Deutschland) sowie andere RE-Verbindungen werden nicht von der NS betrieben und sind mit FIP nicht nutzbar.
+
+Eine Ausnahme gilt für den Dreiländerzug (Liège-Guillemins – Maastricht – Heerlen – Aachen): Zwischen Maastricht und Liège-Guillemins wird der Zug im Auftrag der NS und SNCB betrieben, NS/SNCB-Tickets einschließlich FIP bleiben gültig. Zwischen Maastricht und Aachen (über Heerlen) wird der Zug von Arriva betrieben und FIP ist nicht gültig.
{{% /highlight %}}
{{% /train-category %}}
diff --git a/content/operator/ns/index.en.md b/content/operator/ns/index.en.md
index bcb218833..0778cafaf 100644
--- a/content/operator/ns/index.en.md
+++ b/content/operator/ns/index.en.md
@@ -149,7 +149,9 @@ Unlike in other countries, these are not true long-distance trains, but rather f
Regional trains with more stops than Intercity, but still only at important stations.
{{% highlight confusion %}}
-Trains of the Sneltrein / Regional-Express `RE` category, including the connections Venlo – Hamm (Germany), Maastricht – Aachen (Germany), and Arnhem – Düsseldorf (Germany), as well as other RE connections, are not operated by NS and cannot be used with FIP.
+Trains of the Sneltrein / Regional-Express `RE` category, including the connections Venlo – Hamm (Germany) and Arnhem – Düsseldorf (Germany), as well as other RE connections, are not operated by NS and cannot be used with FIP.
+
+An exception applies to the Three-Country Train (Liège-Guillemins – Maastricht – Heerlen – Aachen): between Maastricht and Liège-Guillemins, the train is operated on behalf of NS and SNCB, and NS/SNCB tickets including FIP remain valid. Between Maastricht and Aachen (via Heerlen), the train is operated by Arriva and FIP is not valid.
{{% /highlight %}}
{{% /train-category %}}
diff --git a/content/operator/ns/index.fr.md b/content/operator/ns/index.fr.md
index 3de5a8ed3..de7610a26 100644
--- a/content/operator/ns/index.fr.md
+++ b/content/operator/ns/index.fr.md
@@ -149,7 +149,9 @@ Contrairement à d’autres pays, il ne s’agit pas de véritables trains longu
Trains régionaux avec plus d’arrêts que les Intercity, mais uniquement dans les gares principales.
{{% highlight confusion %}}
-Les trains de la catégorie Sneltrein / Regional-Express `RE`, notamment les liaisons Venlo – Hamm (Allemagne), Maastricht – Aix-la-Chapelle (Allemagne) et Arnhem – Düsseldorf (Allemagne), ainsi que d’autres liaisons RE, ne sont pas exploités par NS et ne sont pas accessibles avec FIP.
+Les trains de la catégorie Sneltrein / Regional-Express `RE`, notamment les liaisons Venlo – Hamm (Allemagne) et Arnhem – Düsseldorf (Allemagne), ainsi que d’autres liaisons RE, ne sont pas exploités par NS et ne sont pas accessibles avec FIP.
+
+Une exception s’applique au Train des Trois Pays (Liège-Guillemins – Maastricht – Heerlen – Aix-la-Chapelle) : entre Maastricht et Liège-Guillemins, le train est exploité pour le compte de la NS et de la SNCB, et les billets NS/SNCB incluant le FIP restent valables. Entre Maastricht et Aix-la-Chapelle (via Heerlen), le train est exploité par Arriva et le FIP n’est pas valable.
{{% /highlight %}}
{{% /train-category %}}
diff --git a/content/operator/ns/transitous.yaml b/content/operator/ns/transitous.yaml
new file mode 100644
index 000000000..d4bc3c7a5
--- /dev/null
+++ b/content/operator/ns/transitous.yaml
@@ -0,0 +1,52 @@
+- query: (agencyId == "IFF:ARRIVA" && routeShortName == "Sneltrein RE18" || routeId.startsWith("de-DELFI_de:nrw:re18")) && !fromName.includes("Heerlen") && !fromName.includes("Aachen") && !fromName.includes("Herzogenrath") && !fromName.includes("Eygelshoven") && !fromName.includes("Landgraaf") && !toName.includes("Heerlen") && !toName.includes("Aachen") && !toName.includes("Herzogenrath") && !toName.includes("Eygelshoven") && !toName.includes("Landgraaf")
+ fip_accepted: true
+ priority: 3
+ message:
+ de: >-
+ FIP ist auf dem Abschnitt Liège-Guillemins – Maastricht des
+ [Dreiländerzugs (RE18)](/country/belgium/#dreiländerzug-liège--maastricht--heerlen--aachen)
+ gültig. Der Zug wird hier im Auftrag von NS und SNCB betrieben.
+ en: >-
+ FIP is valid on the Liège-Guillemins – Maastricht section of the
+ [Three-Country Train (RE18)](/country/belgium/#three-country-train-liège--maastricht--heerlen--aachen).
+ The train is operated on behalf of NS and SNCB on this section.
+ fr: >-
+ Le FIP est valable sur le tronçon Liège-Guillemins – Maastricht du
+ [Train des Trois Pays (RE18)](/country/belgium/#train-des-trois-pays-liège--maastricht--heerlen--aix-la-chapelle).
+ Le train est exploité pour le compte de la NS et de la SNCB sur ce
+ tronçon.
+- query: (agencyId == "IFF:ARRIVA" && routeShortName == "Sneltrein RE18" || routeId.startsWith("de-DELFI_de:nrw:re18")) && !fromName.includes("Liège") && !fromName.includes("Bressoux") && !fromName.includes("Visé") && !fromName.includes("Eijsden") && !fromName.includes("Maastricht Randwyck") && !toName.includes("Liège") && !toName.includes("Bressoux") && !toName.includes("Visé") && !toName.includes("Eijsden") && !toName.includes("Maastricht Randwyck")
+ fip_accepted: false
+ priority: 3
+ message:
+ de: >-
+ FIP ist auf dem Abschnitt Maastricht – Aachen des
+ [Dreiländerzugs (RE18)](/country/netherlands/#dreiländerzug-liège--maastricht--heerlen--aachen)
+ nicht gültig. Der Zug wird hier von Arriva betrieben.
+ en: >-
+ FIP is not valid on the Maastricht – Aachen section of the
+ [Three-Country Train (RE18)](/country/netherlands/#three-country-train-liège--maastricht--heerlen--aachen).
+ The train is operated by Arriva on this section.
+ fr: >-
+ Le FIP n'est pas valable sur le tronçon Maastricht – Aix-la-Chapelle du
+ [Train des Trois Pays (RE18)](/country/netherlands/#train-des-trois-pays-liège--maastricht--heerlen--aix-la-chapelle).
+ Le train est exploité par Arriva sur ce tronçon.
+- query: agencyId == "IFF:ARRIVA" && routeShortName == "Sneltrein RE18" || routeId.startsWith("de-DELFI_de:nrw:re18")
+ fip_accepted: "partially"
+ priority: 2
+ message:
+ de: >-
+ FIP ist zwischen Liège-Guillemins und Maastricht gültig. Über Maastricht
+ hinaus Richtung Heerlen und Aachen wird der Zug von Arriva betrieben und
+ FIP ist auf diesem Abschnitt nicht gültig.
+ [Mehr Informationen](/country/netherlands/#dreiländerzug-liège--maastricht--heerlen--aachen)
+ en: >-
+ FIP is valid between Liège-Guillemins and Maastricht. Beyond Maastricht
+ towards Heerlen and Aachen, the train is operated by Arriva and FIP is not
+ valid on this section.
+ [More information](/country/netherlands/#three-country-train-liège--maastricht--heerlen--aachen)
+ fr: >-
+ Le FIP est valable entre Liège-Guillemins et Maastricht. Au-delà de
+ Maastricht vers Heerlen et Aix-la-Chapelle, le train est exploité par
+ Arriva et le FIP n'est pas valable sur ce tronçon.
+ [Plus d'informations](/country/netherlands/#train-des-trois-pays-liège--maastricht--heerlen--aix-la-chapelle)
diff --git a/content/operator/oebb/transitous.yaml b/content/operator/oebb/transitous.yaml
new file mode 100644
index 000000000..ac21f0f33
--- /dev/null
+++ b/content/operator/oebb/transitous.yaml
@@ -0,0 +1,3 @@
+- query: agencyName == "OEBB Personenverkehr AG Kundenservice"
+- query: agencyName == "ÖBB-Personenverkehr AG"
+- query: agencyName == "ÖBB"
diff --git a/content/operator/pkp/transitous.yaml b/content/operator/pkp/transitous.yaml
new file mode 100644
index 000000000..66b6f555f
--- /dev/null
+++ b/content/operator/pkp/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "PKP Intercity"
+- query: agencyName == "Polregio"
diff --git a/content/operator/renfe/transitous.yaml b/content/operator/renfe/transitous.yaml
new file mode 100644
index 000000000..525e9cc68
--- /dev/null
+++ b/content/operator/renfe/transitous.yaml
@@ -0,0 +1,6 @@
+- query: agencyName == "RENFE OPERADORA" && routeShortName == "AVE"
+ category: ave
+- query: agencyName == "Renfe"
+- query: agencyName == "Renfe Operadora"
+- query: agencyName == "RENFE"
+- query: agencyName == "RENFE OPERADORA"
diff --git a/content/operator/sbb/transitous.yaml b/content/operator/sbb/transitous.yaml
new file mode 100644
index 000000000..7e2fb931b
--- /dev/null
+++ b/content/operator/sbb/transitous.yaml
@@ -0,0 +1,4 @@
+- query: agencyName == "SBB"
+- query: agencyName == "CFF"
+- query: agencyName == "FFS"
+- query: agencyName == "SBB CFF FFS"
diff --git a/content/operator/sncb/index.de.md b/content/operator/sncb/index.de.md
index c1ffbf573..2dae8dc0d 100644
--- a/content/operator/sncb/index.de.md
+++ b/content/operator/sncb/index.de.md
@@ -209,6 +209,14 @@ Bis zu vier Kinder unter 12 Jahren reisen in Begleitung eines Erwachsenen (Perso
## Tarifliche Besonderheiten
+### Dreiländerzug (Liège – Maastricht – Heerlen – Aachen)
+
+Seit Juni 2024 betreibt Arriva den Dreiländerzug zwischen Aachen (Deutschland), Heerlen (Niederlande), Maastricht (Niederlande) und Liège-Guillemins (Belgien). Auf dem Abschnitt zwischen Liège-Guillemins und Maastricht wird der Zug im Auftrag der SNCB (belgischer Abschnitt) und der NS (niederländischer Abschnitt bis Maastricht) betrieben. SNCB- und NS-Tickets, einschließlich FIP, bleiben auf diesem Abschnitt gültig.[^8]
+
+{{% highlight important %}}
+FIP ist zwischen Liège-Guillemins und Maastricht gültig. Jenseits von Maastricht in Richtung Heerlen und Aachen wird der Zug von Arriva betrieben und FIP ist auf diesem Abschnitt nicht gültig.
+{{% /highlight %}}
+
### Flughafen Brüssel Zaventem
Auf Verbindungen von und zum Flughafen Brüssel Zaventem muss für den FIP Freifahrtschein ein Zuschlag gezahlt werden. Dieser beträgt aktuell 7,10 Euro (vgl. [Info der SNCB](https://www.belgiantrain.be/de/tickets-and-railcards/airports/brussels-airport)) und muss auch gezahlt werden, wenn der Hinweise _No Supplement Necessary_ angegeben ist. [^1] Bei FIP 50 / FIP 75 Tickets ist dieser bereits im Preis inbegriffen, sofern das Ticket nicht am Fahrkartenautomaten erworben wurde. [^2]
@@ -238,3 +246,5 @@ Dieser Betreiber ist Teil des AJC (Agreement on Journey Continuation). [Weitere
[^6]: [Rail Delivery Group -- Changes to buying tickets on SNCB trains in Belgium](https://www.raildeliverygroup.com/rst/stop-press/469782370-changes-to-buying-tickets-on-sncb-trains-in-belgium.html)
[^7]: [FIP Guide Community: SNCB Unlimited Pass](https://discord.com/channels/1250522473188032512/1433782574806728804/1470491831987998771)
+
+[^8]: [Arriva Dreiländerzug](https://www.arriva.nl/over-je-reis/met-de-trein/de-drielandentrein/)
diff --git a/content/operator/sncb/index.en.md b/content/operator/sncb/index.en.md
index 1b5d549b0..de6c8ae5e 100644
--- a/content/operator/sncb/index.en.md
+++ b/content/operator/sncb/index.en.md
@@ -211,6 +211,14 @@ Up to four children under the age of 12 travel for free when accompanied by an a
## Special Tariff Conditions
+### Three-Country Train (Liège – Maastricht – Heerlen – Aachen)
+
+Since June 2024, Arriva operates the Three-Country Train between Aachen (Germany), Heerlen (Netherlands), Maastricht (Netherlands), and Liège-Guillemins (Belgium). On the section between Liège-Guillemins and Maastricht, the train is operated on behalf of SNCB (Belgian section) and NS (Dutch section up to Maastricht). SNCB and NS tickets, including FIP, remain valid on this section.[^8]
+
+{{% highlight important %}}
+FIP is valid between Liège-Guillemins and Maastricht. Beyond Maastricht towards Heerlen and Aachen, the train is operated by Arriva and FIP is not valid on this section.
+{{% /highlight %}}
+
### Brussels Zaventem Airport
For connections to and from Brussels Zaventem Airport, a surcharge must be paid for the FIP Coupon. This currently amounts to € 7.10 (see [SNCB info](https://www.belgiantrain.be/en/tickets-and-railcards/airports/brussels-airport)) and must also be paid if the note _No Supplement Necessary_ is indicated. [^1] For FIP 50 / FIP 75 Tickets, this is already included in the price, unless the ticket was purchased at a ticket machine. [^2]
@@ -240,3 +248,5 @@ This operator is part of AJC (Agreement on Journey Continuation). [More informat
[^6]: [Rail Delivery Group -- Changes to buying tickets on SNCB trains in Belgium](https://www.raildeliverygroup.com/rst/stop-press/469782370-changes-to-buying-tickets-on-sncb-trains-in-belgium.html)
[^7]: [FIP Guide Community: SNCB Unlimited Pass](https://discord.com/channels/1250522473188032512/1433782574806728804/1470491831987998771)
+
+[^8]: [Arriva Three-Country Train](https://www.arriva.nl/over-je-reis/met-de-trein/de-drielandentrein/)
diff --git a/content/operator/sncb/index.fr.md b/content/operator/sncb/index.fr.md
index b25977786..abab0eabe 100644
--- a/content/operator/sncb/index.fr.md
+++ b/content/operator/sncb/index.fr.md
@@ -210,6 +210,14 @@ Jusqu’à quatre enfants de moins de 12 ans voyagent gratuitement lorsqu’ils
## Conditions tarifaires spéciales
+### Train des Trois Pays (Liège – Maastricht – Heerlen – Aix-la-Chapelle)
+
+Depuis juin 2024, Arriva exploite le Train des Trois Pays entre Aix-la-Chapelle (Allemagne), Heerlen (Pays-Bas), Maastricht (Pays-Bas) et Liège-Guillemins (Belgique). Sur la section entre Liège-Guillemins et Maastricht, le train est exploité pour le compte de la SNCB (section belge) et de la NS (section néerlandaise jusqu'à Maastricht). Les billets SNCB et NS, y compris les titres FIP, restent valables sur cette section.[^8]
+
+{{% highlight important %}}
+Le FIP est valable entre Liège-Guillemins et Maastricht. Au-delà de Maastricht en direction de Heerlen et Aix-la-Chapelle, le train est exploité par Arriva et le FIP n'est pas valable sur cette section.
+{{% /highlight %}}
+
### Aéroport de Bruxelles-Zaventem
Un supplément de 7,10 € est requis pour les trajets à destination ou en provenance de l’aéroport, même avec un Coupon FIP, même si le message _"Pas de supplément requis"_ figure sur le billet. [^1] Ce supplément est inclus dans le prix d’un Billet FIP 50 / FIP 75, sauf si le billet a été acheté à un distributeur automatique. [^2] [Plus d’infos sur le supplément SNCB](https://www.belgiantrain.be/fr/tickets-and-railcards/airports/brussels-airport)
@@ -239,3 +247,5 @@ Cet opérateur fait partie de l’AJC (Agreement on Journey Continuation). [Plus
[^6]: [Rail Delivery Group -- Changes to buying tickets on SNCB trains in Belgium](https://www.raildeliverygroup.com/rst/stop-press/469782370-changes-to-buying-tickets-on-sncb-trains-in-belgium.html)
[^7]: [FIP Guide Community: SNCB Unlimited Pass](https://discord.com/channels/1250522473188032512/1433782574806728804/1470491831987998771)
+
+[^8]: [Arriva Train des Trois Pays](https://www.arriva.nl/over-je-reis/met-de-trein/de-drielandentrein/)
diff --git a/content/operator/sncb/transitous.yaml b/content/operator/sncb/transitous.yaml
new file mode 100644
index 000000000..abd0a1944
--- /dev/null
+++ b/content/operator/sncb/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "SNCB"
+- query: agencyName == "NMBS"
diff --git a/content/operator/sncf/transitous.yaml b/content/operator/sncf/transitous.yaml
new file mode 100644
index 000000000..9e40d03be
--- /dev/null
+++ b/content/operator/sncf/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "SNCF"
+- query: agencyName == "SNCF Voyageurs"
diff --git a/content/operator/sz/transitous.yaml b/content/operator/sz/transitous.yaml
new file mode 100644
index 000000000..74ad630ae
--- /dev/null
+++ b/content/operator/sz/transitous.yaml
@@ -0,0 +1,2 @@
+- query: agencyName == "Slovenske železnice"
+- query: agencyName == "SŽ"
diff --git a/content/operator/vy/transitous.yaml b/content/operator/vy/transitous.yaml
new file mode 100644
index 000000000..91583170a
--- /dev/null
+++ b/content/operator/vy/transitous.yaml
@@ -0,0 +1,4 @@
+- query: agencyName == "Vy"
+- query: agencyName == "Vy Tog"
+- query: agencyName == "SJ Nord"
+- query: agencyName == "Go-Ahead Nordic"
diff --git a/hugo.yaml b/hugo.yaml
index 9408f6696..634f770e3 100644
--- a/hugo.yaml
+++ b/hugo.yaml
@@ -1,6 +1,6 @@
enableRobotsTXT: true
enableGitInfo: true
-ignoreLogs: ['warning-rendershortcodes-in-html']
+ignoreLogs: ["warning-rendershortcodes-in-html"]
defaultContentLanguage: "en"
defaultContentLanguageInSubdir: true
@@ -21,6 +21,18 @@ languages:
weight: 3
title: "FIP Guide – Rapide. Clair. Basé sur le partage."
+outputFormats:
+ TransitousMapping:
+ mediaType: application/json
+ baseName: transitous-mapping
+ isPlainText: true
+ notAlternative: true
+
+outputs:
+ home:
+ - HTML
+ - TransitousMapping
+
module:
mounts:
- source: "assets"
diff --git a/i18n/de.yaml b/i18n/de.yaml
index 4d34eba30..f2a334394 100644
--- a/i18n/de.yaml
+++ b/i18n/de.yaml
@@ -224,6 +224,46 @@ related-countries: Verwandte Länder
related-news: Verwandte News
related-operators: Verwandte Betreiber
reportError: Melde ein Fehler
+routePlanner:
+ arrival: Ankunft
+ copyToClipboard: In die Zwischenablage kopieren
+ debugHint: >-
+ Die Information ist nicht korrekt oder es fehlen Informationen? Bitte melde
+ uns den Fehler und hänge die folgenden Informationen an:
+ debugTitle: Debug-Informationen
+ departure: Abfahrt
+ departureTime: Abfahrtszeit
+ duration: Dauer
+ errorGeneric: Ein Fehler ist aufgetreten. Bitte versuche es erneut.
+ experimental: >-
+ Der Routenplaner ist noch experimentell. Er enthält noch Fehler, daher
+ brauchen wir eure Hilfe. Wenn ihr fehlerhafte Informationen findet, meldet
+ diese bitte über das ICON_BUG_REPORT-Symbol für jeden Abschnitt im
+ Routenergebnis. Die Routenauskunft basiert auf der [Transitous
+ API](https://transitous.org/). Bitte prüfe die Informationen vor einer Fahrt
+ unbedingt auf den entsprechenden Betreiberseiten im FIP Guide.
+ fipAccepted: FIP akzeptiert
+ fipNotAccepted: FIP nicht akzeptiert
+ fipPartial: FIP teilweise akzeptiert
+ fipUnknown: FIP-Status unbekannt
+ from: Von
+ fromPlaceholder: z. B. Brüssel-Midi
+ internationalJourney: 'Internationale Fahrt! Bitte prüfe die Grenzpunkte auf den Länderseiten:'
+ loading: Verbindungen werden gesucht…
+ noResults: Keine Verbindungen gefunden.
+ openGitHubIssue: GitHub-Issue öffnen
+ operatorBaseUrl: /de
+ operatorNotInFipGuide: Betreiber nicht im FIP Guide
+ platform: Gleis
+ reservationPartiallyRequired: Reservierung teilweise erforderlich
+ reservationRequired: Reservierung erforderlich
+ search: Suchen
+ swap: Abfahrts- und Ankunftsort tauschen
+ to: Nach
+ toPlaceholder: z. B. Wien Hbf
+ transfer: Umstieg(e)
+ viewInFipGuide: Im FIP Guide ansehen
+ walk: Fußweg
satellite:
content: >-
Um zusätzliche Kosten bei Anrufen ins Ausland zu vermeiden, kann die App
diff --git a/i18n/en.yaml b/i18n/en.yaml
index 976af2d84..55c9e7f3d 100644
--- a/i18n/en.yaml
+++ b/i18n/en.yaml
@@ -217,6 +217,46 @@ related:
operators: Related Operators
operators-nearby: Neighboring Operators
reportError: Report an error
+routePlanner:
+ arrival: Arrival
+ copyToClipboard: Copy to clipboard
+ debugHint: >-
+ Is this information incorrect or missing? Please report the error to us and
+ attach the following information:
+ debugTitle: Debug Information
+ departure: Departure
+ departureTime: Departure time
+ duration: Duration
+ errorGeneric: An error occurred. Please try again.
+ experimental: >-
+ The route planner is still experimental. It still contains errors, so we
+ need your help. If you find incorrect information, please report it using
+ the ICON_BUG_REPORT icon for each leg in the route result. The route
+ information is based on the [Transitous API](https://transitous.org/).
+ Please make sure to verify the information on the respective operator pages
+ in the FIP Guide before traveling.
+ fipAccepted: FIP accepted
+ fipNotAccepted: FIP not accepted
+ fipPartial: FIP partially accepted
+ fipUnknown: FIP status unknown
+ from: From
+ fromPlaceholder: e.g. Brussels-Midi
+ internationalJourney: 'International journey! Please check the border points on the country pages:'
+ loading: Searching for connections…
+ noResults: No connections found.
+ openGitHubIssue: Open GitHub issue
+ operatorBaseUrl: /en
+ operatorNotInFipGuide: Operator not in FIP Guide
+ platform: Platform
+ reservationPartiallyRequired: Reservation partially required
+ reservationRequired: Reservation required
+ search: Search
+ swap: Swap origin and destination
+ to: To
+ toPlaceholder: e.g. Vienna Hbf
+ transfer: transfer(s)
+ viewInFipGuide: View in FIP Guide
+ walk: Walk
satellite:
content: >-
To avoid additional costs when calling abroad, you can use the app
diff --git a/i18n/fr.yaml b/i18n/fr.yaml
index 013b894c0..f6f0f32ac 100644
--- a/i18n/fr.yaml
+++ b/i18n/fr.yaml
@@ -226,6 +226,49 @@ related:
operators: Opérateurs associés
operators-nearby: Opérateurs voisins
reportError: Signaler une erreur
+routePlanner:
+ arrival: Arrivée
+ copyToClipboard: Copier dans le presse-papiers
+ debugHint: >-
+ Cette information est incorrecte ou incomplète ? Merci de nous signaler
+ l’erreur et de joindre les informations suivantes :
+ debugTitle: Informations de débogage
+ departure: Départ
+ departureTime: Heure de départ
+ duration: Durée
+ errorGeneric: Une erreur s'est produite. Veuillez réessayer.
+ experimental: >-
+ Le planificateur d'itinéraire est encore expérimental. Il contient encore
+ des erreurs, c'est pourquoi nous avons besoin de votre aide. Si vous trouvez
+ des informations erronées, veuillez les signaler via l'icône ICON_BUG_REPORT
+ pour chaque tronçon dans le résultat de l'itinéraire. Les informations
+ d'itinéraire sont basées sur l'[API Transitous](https://transitous.org/).
+ Veuillez vérifier les informations sur les pages des opérateurs
+ correspondants dans le FIP Guide avant de voyager.
+ fipAccepted: FIP accepté
+ fipNotAccepted: FIP non accepté
+ fipPartial: FIP partiellement accepté
+ fipUnknown: Statut FIP inconnu
+ from: De
+ fromPlaceholder: ex. Bruxelles-Midi
+ internationalJourney: >-
+ Trajet international ! Veuillez vérifier les points frontières sur les pages
+ des pays :
+ loading: Recherche de connexions…
+ noResults: Aucune connexion trouvée.
+ openGitHubIssue: Ouvrir un ticket GitHub
+ operatorBaseUrl: /fr
+ operatorNotInFipGuide: Opérateur absent du FIP Guide
+ platform: Voie
+ reservationPartiallyRequired: Réservation partiellement obligatoire
+ reservationRequired: Réservation obligatoire
+ search: Rechercher
+ swap: Échanger l'origine et la destination
+ to: Vers
+ toPlaceholder: ex. Vienne Hbf
+ transfer: correspondance(s)
+ viewInFipGuide: Voir dans le FIP Guide
+ walk: À pied
satellite:
content: >-
Pour éviter des frais supplémentaires lors d'appels à l'étranger, vous
diff --git a/layouts/_default/home.transitousmapping.json b/layouts/_default/home.transitousmapping.json
new file mode 100644
index 000000000..821946ca6
--- /dev/null
+++ b/layouts/_default/home.transitousmapping.json
@@ -0,0 +1,66 @@
+{{- $operators := where (where site.Pages "Type" "operator") "IsPage" true -}}
+{{- $result := slice -}}
+{{- range $operators -}}
+ {{- $page := . -}}
+ {{- with .Resources.GetMatch "transitous.yaml" -}}
+ {{- $mapping := . | transform.Unmarshal -}}
+ {{- $trainCategories := $page.Store.Get "train_categories" | default slice -}}
+ {{- $entries := slice -}}
+ {{- range $mapping -}}
+ {{- $priority := .priority | default 0 -}}
+ {{- $entry := dict "query" .query "priority" $priority -}}
+ {{- with .message -}}
+ {{- $msg := index . $page.Language.Lang -}}
+ {{- if $msg -}}
+ {{- $entry = merge $entry (dict "message" ($msg | $page.RenderString)) -}}
+ {{- end -}}
+ {{- end -}}
+ {{- if .category -}}
+ {{- $entry = merge $entry (dict "category" .category) -}}
+ {{- $catId := .category -}}
+ {{- range $trainCategories -}}
+ {{- if eq .id $catId -}}
+ {{- $entry = merge $entry (dict "fipAccepted" .fip_accepted "reservationRequired" .reservation_required) -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- if isset . "fip_accepted" -}}
+ {{- $entry = merge $entry (dict "fipAccepted" .fip_accepted) -}}
+ {{- end -}}
+ {{- if isset . "reservation_required" -}}
+ {{- $entry = merge $entry (dict "reservationRequired" .reservation_required) -}}
+ {{- end -}}
+ {{- $entries = $entries | append $entry -}}
+ {{- end -}}
+ {{- $entries = sort $entries "priority" "desc" -}}
+ {{- $op := dict "operator" $page.File.ContentBaseName "mapping" $entries -}}
+ {{- $result = $result | append $op -}}
+ {{- end -}}
+{{- end -}}
+{{- $noFipEntries := slice -}}
+{{- $countries := where (where site.Pages "Type" "country") "IsPage" true -}}
+{{- range $countries -}}
+ {{- $countryPage := . -}}
+ {{- $countrySlug := .File.ContentBaseName -}}
+ {{- with .Resources.GetMatch "transitous.yaml" -}}
+ {{- $entries := . | transform.Unmarshal -}}
+ {{- range $entries -}}
+ {{- if .query -}}
+ {{- $priority := .priority | default 0 -}}
+ {{- $noFipEntry := dict "query" .query "fipAccepted" false "country" $countrySlug "priority" $priority -}}
+ {{- with .message -}}
+ {{- $msg := index . $countryPage.Language.Lang -}}
+ {{- if $msg -}}
+ {{- $noFipEntry = merge $noFipEntry (dict "message" ($msg | $countryPage.RenderString)) -}}
+ {{- end -}}
+ {{- end -}}
+ {{- $noFipEntries = $noFipEntries | append $noFipEntry -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+{{- end -}}
+{{- if $noFipEntries -}}
+ {{- $noFipEntries = sort $noFipEntries "priority" "desc" -}}
+ {{- $result = $result | append (dict "operator" "no-fip" "mapping" $noFipEntries) -}}
+{{- end -}}
+{{- $result | jsonify (dict "indent" " ") -}}
diff --git a/layouts/_partials/snackbar.html b/layouts/_partials/snackbar.html
index 9659eb04a..c35639f3f 100644
--- a/layouts/_partials/snackbar.html
+++ b/layouts/_partials/snackbar.html
@@ -1,5 +1,6 @@
+
+
+
+
+ {{ $icon := partial "icon" "bug_report" }}
+ {{ $rendered := T "routePlanner.experimental" | .RenderString }}
+ {{ $text := replace $rendered "ICON_BUG_REPORT" $icon | safeHTML }}
+ {{ partial "highlight" (dict "Type" "important" "Content" $text) }}
+
+
+
+
+
+
+
+ {{ T "routePlanner.loading" }}
+
+
+
+
+
+
+
+
+
+{{ end }}
diff --git a/package-lock.json b/package-lock.json
index 9c4a38111..0a6b1865c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@fontsource/roboto": "^5.2.9",
"@fontsource/sansita": "^5.2.8",
"@material-symbols/svg-700": "^0.43.0",
+ "@motis-project/motis-client": "^2.10.2",
"@panzoom/panzoom": "^4.6.1",
"pagefind": "^1.5.2"
},
@@ -38,12 +39,31 @@
"url": "https://github.com/sponsors/ayuhito"
}
},
+ "node_modules/@hey-api/client-fetch": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.4.4.tgz",
+ "integrity": "sha512-ebh1JjUdMAqes/Rg8OvbjDqGWGNhgHgmPtHlkIOUtj3y2mUXqX2g9sVoI/rSKW/FdADPng/90k5AL7bwT8W2lA==",
+ "deprecated": "Starting with v0.73.0, this package is bundled directly inside @hey-api/openapi-ts.",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/hey-api"
+ }
+ },
"node_modules/@material-symbols/svg-700": {
"version": "0.43.0",
"resolved": "https://registry.npmjs.org/@material-symbols/svg-700/-/svg-700-0.43.0.tgz",
"integrity": "sha512-apCxQvQ1eZrxvgvr+CbwnPvN6aS0hn9EgGm+vI3bzEMdE1gqZ4Tk8liDuAhEXdFyYVX6CDUzz/zt+6lCufmSVg==",
"license": "Apache-2.0"
},
+ "node_modules/@motis-project/motis-client": {
+ "version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/@motis-project/motis-client/-/motis-client-2.10.2.tgz",
+ "integrity": "sha512-6HSznDh+iHDxPZXGRG5L8gHXztiTa9Zr+/ZPzOf9wc3JeYQHYlSh5SnUpMrhEch03fQ82EdnQkZ444i3hOW7SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@hey-api/client-fetch": "^0.4.4"
+ }
+ },
"node_modules/@pagefind/darwin-arm64": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz",
diff --git a/package.json b/package.json
index 5159d306e..bf5d3af84 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@fontsource/roboto": "^5.2.9",
"@fontsource/sansita": "^5.2.8",
"@material-symbols/svg-700": "^0.43.0",
+ "@motis-project/motis-client": "^2.10.2",
"@panzoom/panzoom": "^4.6.1",
"pagefind": "^1.5.2"
},