diff --git a/archetypes/operator/index.en.md b/archetypes/operator/index.en.md index 31fe5d27..e99b24e2 100644 --- a/archetypes/operator/index.en.md +++ b/archetypes/operator/index.en.md @@ -8,6 +8,11 @@ country: - "country2" - "country3" operator: "{{ .File.ContentBaseName }}" +Params: + fip_coupon: true # + fip_coupon_relatives: false # + fip_50_ticket: true # + fip_global_fare: true # --- @@ -30,15 +35,14 @@ operator: "{{ .File.ContentBaseName }}" ## Validity of FIP Tickets +{{< fip-validity >}}{{< /fip-validity >}} + -FIP Coupon: <✅/⛔> \ -FIP Coupon for relatives: <✅/⛔> \ -FIP 50 Tickets: <✅/⛔> \ -FIP Global Fare: <✅/⛔> - diff --git a/assets/js/main.js b/assets/js/main.js index b2b525c6..14301965 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -8,3 +8,4 @@ import "./dropdown.js"; import "./darkmode.js"; import "./search.js"; import "./interactiveMap.js"; +import "./modal.js"; diff --git a/assets/js/modal.js b/assets/js/modal.js new file mode 100644 index 00000000..19901ff1 --- /dev/null +++ b/assets/js/modal.js @@ -0,0 +1,50 @@ +document.addEventListener("DOMContentLoaded", () => { + const modalTriggers = document.querySelectorAll("[data-modal-trigger]"); + const modalCloseButtons = document.querySelectorAll("[data-modal-close]"); + + modalTriggers.forEach((trigger) => { + trigger.addEventListener("click", (e) => { + e.preventDefault(); + const modalId = trigger.getAttribute("data-modal-trigger"); + const modal = document.getElementById(modalId); + if (modal) { + openModal(modal); + } + }); + }); + + modalCloseButtons.forEach((button) => { + button.addEventListener("click", () => { + const modal = button.closest(".o-modal"); + if (modal) { + closeModal(modal); + } + }); + }); + + document.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + const openModal = document.querySelector('.o-modal[aria-hidden="false"]'); + if (openModal) { + closeModal(openModal); + } + } + }); + + function openModal(modal) { + modal.setAttribute("aria-hidden", "false"); + document.body.style.overflow = "hidden"; + + const firstFocusable = modal.querySelector( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', + ); + if (firstFocusable) { + firstFocusable.focus(); + } + } + + function closeModal(modal) { + modal.setAttribute("aria-hidden", "true"); + document.body.style.overflow = ""; + } +}); diff --git a/assets/sass/_variables.scss b/assets/sass/_variables.scss index 5a55b60b..7f5bc397 100644 --- a/assets/sass/_variables.scss +++ b/assets/sass/_variables.scss @@ -14,6 +14,14 @@ $bg-code: #fff284; $color-onLight: #000000; $color-table-border: #5b5b5b; +$fip-validity-accepted: #155724; +$fip-validity-rejected: #b70000; +$fip-validity-warning: #b64900; + +$fip-validity-accepted-dark: #a8d5ba; +$fip-validity-rejected-dark: #f5a9ae; +$fip-validity-warning-dark: #f0d98d; + html { --pagefind-ui-scale: 1 !important; --pagefind-ui-text: #000; @@ -27,6 +35,9 @@ html { --color-onLight: #{$color-onLight}; --color-table-border: #{$color-table-border}; --color-body: rgb(33, 37, 41); + --fip-validity-accepted: #{$fip-validity-accepted}; + --fip-validity-rejected: #{$fip-validity-rejected}; + --fip-validity-warning: #{$fip-validity-warning}; --border-radius-s: 0.4rem; --border-radius-m: 0.8rem; --border-radius-l: 1.6rem; @@ -57,6 +68,9 @@ html[data-theme="dark"] { --color-onLight: #ffffff; --color-table-border: #555; --color-body: #e0e0e0; + --fip-validity-accepted: #{$fip-validity-accepted-dark}; + --fip-validity-rejected: #{$fip-validity-rejected-dark}; + --fip-validity-warning: #{$fip-validity-warning-dark}; --pagefind-ui-border: #555; --box-shadow: 0 0.4rem 1rem rgba(0, 0, 0, 0.5); --box-shadow-light: 0.4rem 0.4rem 0.4rem rgba(0, 0, 0, 0.3); diff --git a/assets/sass/fipValidity.scss b/assets/sass/fipValidity.scss new file mode 100644 index 00000000..7b29ceb2 --- /dev/null +++ b/assets/sass/fipValidity.scss @@ -0,0 +1,12 @@ +.o-fip-validity { + display: flex; + flex-direction: column; + gap: 0.8rem; + margin-bottom: 1.2rem; + + &__tags { + display: flex; + flex-direction: column; + gap: 0.8rem; + } +} diff --git a/assets/sass/fipValidityItem.scss b/assets/sass/fipValidityItem.scss new file mode 100644 index 00000000..24cf6b7c --- /dev/null +++ b/assets/sass/fipValidityItem.scss @@ -0,0 +1,71 @@ +.a-fip-validity-item { + display: flex; + align-items: flex-start; + gap: 0.5rem; + font-size: 0.95em; + font-weight: 400; + line-height: 1.2em; + + svg { + margin-top: 0.15em; + } + + &--success { + color: var(--fip-validity-accepted); + } + + &--error { + color: var(--fip-validity-rejected); + } + + &--info { + color: var(--color-body); + opacity: 0.85; + } + + &__content { + display: flex; + flex-direction: column; + gap: 0.2rem; + } + + &__text { + display: flex; + align-items: center; + gap: 0.3rem; + } + + &__info-button { + background: none; + border: none; + padding: 0; + cursor: pointer; + color: var(--color-body); + display: inline-flex; + align-items: center; + justify-content: center; + opacity: 0.7; + transition: opacity 0.2s ease; + + &:hover { + opacity: 1; + } + + svg { + width: 1.2em; + height: 1.2em; + margin-top: 0; + } + } + + &__note { + font-size: 0.8em; + color: var(--color-body); + font-weight: 400; + + p { + margin: 0; + display: inline; + } + } +} diff --git a/assets/sass/main.scss b/assets/sass/main.scss index 639c39d7..c8323ba1 100644 --- a/assets/sass/main.scss +++ b/assets/sass/main.scss @@ -17,3 +17,6 @@ @import "startpage.scss"; @import "interactiveMap.scss"; @import "dropdown.scss"; +@import "fipValidity.scss"; +@import "fipValidityItem.scss"; +@import "modal.scss"; diff --git a/assets/sass/modal.scss b/assets/sass/modal.scss new file mode 100644 index 00000000..b9a464cf --- /dev/null +++ b/assets/sass/modal.scss @@ -0,0 +1,139 @@ +.o-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + + &[aria-hidden="false"] { + opacity: 1; + pointer-events: auto; + } + + &__overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + } + + &__container { + position: relative; + background: var(--bg-default); + border-radius: var(--border-radius-m); + box-shadow: var(--box-shadow); + max-width: 90vw; + max-height: 90vh; + width: 60rem; + display: flex; + flex-direction: column; + z-index: 1; + } + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem; + border-bottom: var(--border); + } + + &__title { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + } + + &__close { + background: none; + border: none; + padding: 0.5rem; + cursor: pointer; + color: var(--color-body); + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--border-radius-s); + transition: background 0.2s ease; + + &:hover { + background: var(--bg-neutral); + } + + svg { + width: 1.5rem; + height: 1.5rem; + } + } + + &__content { + padding: 0 1.5rem 1.5rem 1.5rem; + overflow: hidden; + display: flex; + flex-direction: column; + } + + &__description { + margin-bottom: 1.5rem; + color: var(--color-body); + flex-shrink: 0; + } + + &__table-wrapper { + overflow-y: auto; + flex: 1; + min-height: 0; + } + + &__table { + width: 100%; + margin-left: 0; + margin-bottom: 0; + + &--body { + margin-top: 0; + } + + a { + color: var(--link-default); + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + &__status { + display: flex; + align-items: center; + gap: 0.5rem; + + svg { + width: 1.2rem; + height: 1.2rem; + } + + &--yes { + color: var(--fip-validity-accepted); + } + + &--no { + color: var(--fip-validity-rejected); + } + + &--unknown { + color: var(--color-body); + opacity: 0.6; + } + } +} diff --git a/content/operator/sncf/index.de.md b/content/operator/sncf/index.de.md index 61231757..8b6334a9 100644 --- a/content/operator/sncf/index.de.md +++ b/content/operator/sncf/index.de.md @@ -10,6 +10,19 @@ country: - "belgium" - "luxembourg" operator: "sncf" +Params: + fip_coupon: true + fip_coupon_fields: + oebb: 4 + renfe: 2 + fip_coupon_relatives: + oebb: true + renfe: false + fip_50_ticket: true + fip_50_ticket_discount: + oebb: 25 + renfe: 75 + fip_global_fare: true --- Die SNCF (Société Nationale des Chemins de fer Français) ist die französische Staatsbahn und die wichtigste Bahngesellschaft in Frankreich. Sie betreibt fast alle Fern- und Regionalzüge in Frankreich. @@ -24,10 +37,7 @@ Die SNCF (Société Nationale des Chemins de fer Français) ist die französisch ## Gültigkeit FIP Tickets -FIP Freifahrtschein: ✅ \ -FIP Freifahrt Angehörige: ⛔ \ -FIP 50 Tickets: ✅ \ -FIP Globalpreis: ✅ (Für internationale `TGV` Züge, siehe [Grenzüberschreitende TGV inOui / ICE Züge](#grenzüberschreitende-tgv-inoui--ice-züge)) +{{< fip-validity fip_global_fare_footnote="Für internationale `TGV` Züge, siehe [Grenzüberschreitende TGV inOui / ICE Züge](#grenzüberschreitende-tgv-inoui--ice-züge)">}} FIP Freifahrtscheine und FIP 50 Tickets sind auf Verbindungen der SNCF gültig. Bei grenzüberschreitenden Fahrten im Nahverkehr muss entweder ein durchgängiges FIP 50 Ticket oder FIP Freifahrtscheine beider Länder vorhanden sein. Auf internationalen Fernverkehrsverbindungen mittels `TGV` oder `ICE` gelten jedoch Globalpreise, siehe [Grenzüberschreitende TGV inOui / ICE Züge](#grenzüberschreitende-tgv-inoui--ice-züge). diff --git a/content/operator/sncf/index.en.md b/content/operator/sncf/index.en.md index a7b6c2f1..ef7cd364 100644 --- a/content/operator/sncf/index.en.md +++ b/content/operator/sncf/index.en.md @@ -10,6 +10,19 @@ country: - "belgium" - "luxembourg" operator: "sncf" +Params: + fip_coupon: true + fip_coupon_fields: + oebb: 4 + renfe: 2 + fip_coupon_relatives: + oebb: true + renfe: false + fip_50_ticket: true + fip_50_ticket_discount: + oebb: 25 + renfe: 75 + fip_global_fare: true --- SNCF (Société Nationale des Chemins de fer Français) is the French national railway company and the main rail operator in France. It operates almost all long-distance and regional trains in France. @@ -24,10 +37,7 @@ SNCF (Société Nationale des Chemins de fer Français) is the French national r ## Validity of FIP Tickets -FIP Coupon: ✅ \ -FIP Coupon for relatives: ⛔ \ -FIP 50 Ticket: ✅ \ -FIP Global Fare: ✅ (For international `TGV` trains, see [International TGV inOui / ICE trains](#international-tgv-inoui--ice-trains)) +{{< fip-validity fip_global_fare_footnote="Only for international `TGV` trains, see [International TGV inOui / ICE trains](#international-tgv-inoui--ice-trains)">}} FIP Coupons and FIP 50 Tickets are valid on SNCF services. For cross-border journeys on local trains, either a continuous FIP 50 Ticket or FIP Coupons for both countries are required. For international long-distance services (`TGV` or `ICE`), global fares apply (see [International TGV inOui / ICE trains](#international-tgv-inoui--ice-trains)). diff --git a/content/operator/sncf/index.fr.md b/content/operator/sncf/index.fr.md index d6b12f4f..bc76b21d 100644 --- a/content/operator/sncf/index.fr.md +++ b/content/operator/sncf/index.fr.md @@ -10,6 +10,19 @@ country: - "belgium" - "luxembourg" operator: "sncf" +Params: + fip_coupon: true + fip_coupon_fields: + oebb: 4 + renfe: 2 + fip_coupon_relatives: + oebb: true + renfe: false + fip_50_ticket: true + fip_50_ticket_discount: + oebb: 25 + renfe: 75 + fip_global_fare: true --- La SNCF (Société Nationale des Chemins de fer Français) est la compagnie ferroviaire nationale française et le principal opérateur ferroviaire en France. Elle exploite la quasi-totalité des trains grandes lignes et régionaux du pays. @@ -24,10 +37,7 @@ La SNCF (Société Nationale des Chemins de fer Français) est la compagnie ferr ## Validité des Billets FIP -Coupon FIP : ✅ \ -Coupon FIP accompagnant : ⛔ \ -Billet FIP 50 : ✅ \ -Tarif Global FIP : ✅ (pour les trains internationaux `TGV`, voir [Trains TGV inOui / ICE internationaux](#trains-tgv-inoui--ice-internationaux)) +{{< fip-validity fip_global_fare_footnote="pour les trains internationaux `TGV`, voir [Trains TGV inOui / ICE internationaux](#trains-tgv-inoui--ice-internationaux)">}} Les Coupons FIP et Billets FIP 50 sont valables sur les services SNCF. Pour les trajets transfrontaliers en trains régionaux, il faut soit un Billet FIP 50 continu, soit des Coupons FIP valables dans chaque pays. Sur les trains grandes lignes internationaux (`TGV` ou `ICE`), des Tarifs Globaux s’appliquent (voir [Trains TGV inOui / ICE internationaux](#trains-tgv-inoui--ice-internationaux)). diff --git a/i18n/de.yaml b/i18n/de.yaml index d17e52bd..4001c2c8 100644 --- a/i18n/de.yaml +++ b/i18n/de.yaml @@ -27,6 +27,38 @@ country: other: Länder discord: FIP Guide Community editPage: Seite bearbeiten +fipValidity: + close-modal: Schließen + discount: Rabatt + fields: Felder + fip-50-ticket: FIP 50 Ticket + fip-50-ticket-discount-modal-description: >- + Der Rabatt auf den regulären Fahrpreis für FIP 50 Tickets hängt von der + Bahngesellschaft ab, die deinen FIP Ausweis ausgestellt hat. + fip-50-ticket-discount-modal-title: FIP 50 Ticket Rabatt nach Betreiber + fip-coupon: FIP Freifahrtschein + fip-coupon-fields-modal-description: >- + Die maximale Anzahl der Felder auf dem FIP Freifahrtschein hängt von der + Bahngesellschaft ab, die deinen FIP Ausweis ausgestellt hat. + fip-coupon-fields-modal-title: FIP Freifahrtschein Felder nach Betreiber + fip-coupon-fields-value: '%d Felder' + fip-coupon-relatives: FIP Freifahrt Angehörige + fip-global-fare: FIP Globalpreis + issuer: Aussteller des FIP Ausweises + relatives-available: FIP Freifahrt Angehörige verfügbar + relatives-modal-description: >- + Die Verfügbarkeit von FIP Freifahrt Angehörige hängt von der + Bahngesellschaft ab, die deinen FIP Ausweis ausgestellt hat. + relatives-modal-title: FIP Freifahrt Angehörige nach Betreiber + relatives-status-no: Nicht verfügbar + relatives-status-unknown: Unbekannt + relatives-status-yes: Verfügbar + show-50-ticket-discount-info: Informationen zu FIP 50 Ticket Rabatten anzeigen + show-coupon-fields-info: Informationen zu FIP Freifahrtschein Feldern anzeigen + show-relatives-info: Informationen zu FIP Freifahrt Angehörige anzeigen + status-no: Nicht akzeptiert + status-unknown: Unbekannt + status-yes: Akzeptiert footer-love: aria-label: Made with love in Europe text: Made with ♥️ in Europe diff --git a/i18n/en.yaml b/i18n/en.yaml index 6730f81d..f1ae7d6b 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -26,6 +26,38 @@ country: other: countries discord: FIP Guide Community editPage: Edit page +fipValidity: + close-modal: Close + discount: Discount + fields: Fields + fip-50-ticket: FIP 50 Ticket + fip-50-ticket-discount-modal-description: >- + The discount on the regular fare for FIP 50 Tickets depends on the railway + company that issued your FIP card. + fip-50-ticket-discount-modal-title: FIP 50 Ticket Discount by Operator + fip-coupon: FIP Coupon + fip-coupon-fields-modal-description: >- + The maximum number of fields on the FIP Coupon depends on the railway + company that issued your FIP card. + fip-coupon-fields-modal-title: FIP Coupon Fields by Operator + fip-coupon-fields-value: '%d fields' + fip-coupon-relatives: FIP Coupon for relatives + fip-global-fare: FIP Global Fare + issuer: Issuer of the FIP card + relatives-available: FIP Coupon for relatives available + relatives-modal-description: >- + The availability of FIP Coupon for relatives depends on the railway company + that issued your FIP card. + relatives-modal-title: FIP Coupon for relatives by Operator + relatives-status-no: Not available + relatives-status-unknown: Unknown + relatives-status-yes: Available + show-50-ticket-discount-info: Show information about FIP 50 Ticket discounts + show-coupon-fields-info: Show information about FIP Coupon fields + show-relatives-info: Show information about FIP Coupon for relatives + status-no: Not accepted + status-unknown: Unknown + status-yes: Accepted footer-love: aria-label: Made with love in Europe text: Made with ♥️ in Europe diff --git a/i18n/fr.yaml b/i18n/fr.yaml index bb4bd95d..56b5cfb5 100644 --- a/i18n/fr.yaml +++ b/i18n/fr.yaml @@ -27,6 +27,38 @@ country: countryselection: Choisir un pays discord: Communauté FIP Guide editPage: Modifier la page +fipValidity: + close-modal: Fermer + discount: Réduction + fields: Champs + fip-50-ticket: Billet FIP 50 + fip-50-ticket-discount-modal-description: >- + La réduction sur le tarif régulier pour les Billets FIP 50 dépend de la + compagnie ferroviaire qui a émis votre carte FIP. + fip-50-ticket-discount-modal-title: Réduction Billet FIP 50 par opérateur + fip-coupon: Coupon FIP + fip-coupon-fields-modal-description: >- + Le nombre maximum de champs sur le Coupon FIP dépend de la compagnie + ferroviaire qui a émis votre carte FIP. + fip-coupon-fields-modal-title: Champs du Coupon FIP par opérateur + fip-coupon-fields-value: '%d champs' + fip-coupon-relatives: Coupon FIP accompagnant + fip-global-fare: Tarif Global FIP + issuer: Émetteur de la carte FIP + relatives-available: Coupon FIP accompagnant disponible + relatives-modal-description: >- + La disponibilité du Coupon FIP accompagnant dépend de la compagnie + ferroviaire qui a émis votre carte FIP. + relatives-modal-title: Coupon FIP accompagnant par opérateur + relatives-status-no: Non disponible + relatives-status-unknown: Inconnu + relatives-status-yes: Disponible + show-50-ticket-discount-info: Afficher les informations sur les réductions Billet FIP 50 + show-coupon-fields-info: Afficher les informations sur les champs du Coupon FIP + show-relatives-info: Afficher les informations sur le Coupon FIP accompagnant + status-no: Non accepté + status-unknown: Inconnu + status-yes: Accepté footer-love: aria-label: Fait avec amour en Europe text: Fait avec ♥️ en Europe diff --git a/layouts/partials/fip-validity-item.html b/layouts/partials/fip-validity-item.html new file mode 100644 index 00000000..8bc18575 --- /dev/null +++ b/layouts/partials/fip-validity-item.html @@ -0,0 +1,324 @@ +
+ {{ partial "icon" .Icon }} +
+ + {{ i18n .Text }} + {{- if .ShowCouponFieldsInfo -}} + + {{- end -}} + {{- if .Show50TicketDiscountInfo -}} + + {{- end -}} + {{- if .ShowRelativesInfo -}} + + {{- end -}} + + {{- with .Note -}} + {{ . }} + {{- end -}} +
+
+ +{{- if .ShowCouponFieldsInfo -}} + +{{- end -}} + +{{- if .Show50TicketDiscountInfo -}} + +{{- end -}} + +{{- if .ShowRelativesInfo -}} + +{{- end -}} diff --git a/layouts/shortcodes/fip-validity.html b/layouts/shortcodes/fip-validity.html new file mode 100644 index 00000000..12527513 --- /dev/null +++ b/layouts/shortcodes/fip-validity.html @@ -0,0 +1,77 @@ +{{- $page := .Page -}} + + +
+
+ {{- if isset $page.Params "fip_coupon" -}} + {{- $accepted := $page.Params.fip_coupon -}} + {{- $note := "" -}} + {{- with .Get "fip_coupon_footnote" -}} + {{- $note = $page.RenderString . -}} + {{- end -}} + {{- partial "fip-validity-item" ( + dict + "Icon" (cond $accepted "check_circle" "cancel") + "Text" "fipValidity.fip-coupon" + "Type" (cond $accepted "success" "error") + "Note" $note + "ShowCouponFieldsInfo" (isset $page.Params "fip_coupon_fields") + "Page" $page + ) + }} + {{- end -}} + + {{- if isset $page.Params "fip_coupon_relatives" -}} + {{- $note := "" -}} + + {{- with .Get "fip_coupon_relatives_footnote" -}} + {{- $note = $page.RenderString . -}} + {{- end -}} + + {{- partial "fip-validity-item" ( + dict + "Icon" "help" + "Text" "fipValidity.fip-coupon-relatives" + "Type" "info" + "Note" $note + "ShowRelativesInfo" true + "Page" $page + ) + }} + {{- end -}} + + {{- if isset $page.Params "fip_50_ticket" -}} + {{- $accepted := $page.Params.fip_50_ticket -}} + {{- $note := "" -}} + {{- with .Get "fip_50_ticket_footnote" -}} + {{- $note = $page.RenderString . -}} + {{- end -}} + {{- partial "fip-validity-item" ( + dict + "Icon" (cond $accepted "check_circle" "cancel") + "Text" "fipValidity.fip-50-ticket" + "Type" (cond $accepted "success" "error") + "Note" $note + "Show50TicketDiscountInfo" $accepted + "Page" $page + ) + }} + {{- end -}} + + {{- if isset $page.Params "fip_global_fare" -}} + {{- $accepted := $page.Params.fip_global_fare -}} + {{- $note := "" -}} + {{- with .Get "fip_global_fare_footnote" -}} + {{- $note = $page.RenderString . -}} + {{- end -}} + {{- partial "fip-validity-item" ( + dict + "Icon" (cond $accepted "check_circle" "cancel") + "Text" "fipValidity.fip-global-fare" + "Type" (cond $accepted "success" "error") + "Note" $note + ) + }} + {{- end -}} +
+