diff options
63 files changed, 3986 insertions, 1122 deletions
@@ -2,12 +2,15 @@ travelynx - Railway Travel Logger --- [travelynx](https://finalrewind.org/projects/travelynx/) allows checking into -and out of individual trains, thus providing a log of your railway journeys -annotated with real-time delays and service messages. It supports german -railways and trains exposed by the Deutsche Bahn [IRIS +individual public transit vehicles (e.g. buses, ferries, trams, trains) across +most of Germany, Switzerland, Austria, Luxembourg, Ireland, and parts of the +USA. Thus, it provides a log of your railway journeys annotated with real-time +delays and service messages, if available. It supports german railways and +trains exposed by the Deutsche Bahn [IRIS Interface](https://finalrewind.org/projects/Travel-Status-DE-IRIS/) as well as -local transit and some trains outside of germany exposed by the Deutsche Bahn -[HAFAS Interface](https://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/). +regional and local transit exposed by supported [HAFAS +Instances](https://finalrewind.org/projects/Travel-Status-DE-HAFAS/). Support +for EFA instances and bahn.de is under way. You can use the public instance on [travelynx.de](https://travelynx.de) or host your own. See the Installation and Setup notes below. @@ -79,6 +82,12 @@ Please open an issue on <https://github.com/derf/travelynx/issues> or send a mail to derf+travelynx@finalrewind.org if there is anything missing or ambiguous in this setup manual. +Note that Deutsche Bahn have put parts of their API behind an IP reputation +filter. In general, checkins with the bahn.de backend will only be possible if +travelynx is accessing it from a residential (non-server) IP range. See the +dbris bahn.de proxy / proxies setting in `example/travelynx.conf` for +workarounds. + Updating --- @@ -6,6 +6,8 @@ requires 'DateTime::Format::Strptime'; requires 'Email::Sender::Simple'; requires 'GIS::Distance'; requires 'GIS::Distance::Fast'; +requires 'IO::Socket::Socks', '>= 0.64'; +requires 'IO::Socket::SSL', '>= 2.009'; requires 'List::UtilsBy'; requires 'Math::Polygon'; requires 'MIME::Entity'; @@ -15,9 +17,10 @@ requires 'Mojolicious::Plugin::OAuth2'; requires 'Mojo::Pg'; requires 'Text::CSV'; requires 'Text::Markdown'; -requires 'Travel::Status::DE::DBWagenreihung', '== 0.18'; requires 'Travel::Status::DE::EFA'; -requires 'Travel::Status::DE::HAFAS', '>= 5.03'; +requires 'Travel::Status::MOTIS', '>= 0.01'; +requires 'Travel::Status::DE::DBRIS', '>= 0.10'; +requires 'Travel::Status::DE::HAFAS', '>= 6.20'; requires 'Travel::Status::DE::IRIS'; requires 'UUID::Tiny'; requires 'JSON'; diff --git a/cpanfile.snapshot b/cpanfile.snapshot index e71f91d..e44944e 100644 --- a/cpanfile.snapshot +++ b/cpanfile.snapshot @@ -118,10 +118,10 @@ DISTRIBUTIONS URI 0 URI::Escape 0 perl 5.008004 - Alien-Libxml2-0.19 - pathname: P/PL/PLICEASE/Alien-Libxml2-0.19.tar.gz + Alien-Libxml2-0.20 + pathname: P/PL/PLICEASE/Alien-Libxml2-0.20.tar.gz provides: - Alien::Libxml2 0.19 + Alien::Libxml2 0.20 requirements: Alien::Base 2.37 Alien::Build 2.37 @@ -382,8 +382,8 @@ DISTRIBUTIONS Test::More 0.88 Time::HiRes 0 version 0 - DBI-1.645 - pathname: H/HM/HMBRAND/DBI-1.645.tgz + DBI-1.647 + pathname: H/HM/HMBRAND/DBI-1.647.tgz provides: Bundle::DBI 12.008696 DBD::DBM 0.08 @@ -439,7 +439,7 @@ DISTRIBUTIONS DBD::Sponge::dr 12.010003 DBD::Sponge::st 12.010003 DBDI 12.015129 - DBI 1.645 + DBI 1.647 DBI::Const::GetInfo::ANSI 2.008697 DBI::Const::GetInfo::ODBC 2.011374 DBI::Const::GetInfoReturn 2.008697 @@ -479,7 +479,7 @@ DISTRIBUTIONS DBI::SQL::Nano::Table_ 1.015544 DBI::Util::CacheMemory 0.010315 DBI::Util::_accessor 0.009479 - DBI::common 1.645 + DBI::common 1.647 requirements: ExtUtils::MakeMaker 6.48 Test::Simple 0.90 @@ -496,19 +496,19 @@ DISTRIBUTIONS perl 5.012 strict 0 warnings 0 - DateTime-1.65 - pathname: D/DR/DROLSKY/DateTime-1.65.tar.gz - provides: - DateTime 1.65 - DateTime::Duration 1.65 - DateTime::Helpers 1.65 - DateTime::Infinite 1.65 - DateTime::Infinite::Future 1.65 - DateTime::Infinite::Past 1.65 - DateTime::LeapSecond 1.65 - DateTime::PP 1.65 - DateTime::PPExtra 1.65 - DateTime::Types 1.65 + DateTime-1.66 + pathname: D/DR/DROLSKY/DateTime-1.66.tar.gz + provides: + DateTime 1.66 + DateTime::Duration 1.66 + DateTime::Helpers 1.66 + DateTime::Infinite 1.66 + DateTime::Infinite::Future 1.66 + DateTime::Infinite::Past 1.66 + DateTime::LeapSecond 1.66 + DateTime::PP 1.66 + DateTime::PPExtra 1.66 + DateTime::Types 1.66 requirements: Carp 0 DateTime::Locale 1.06 @@ -518,7 +518,7 @@ DISTRIBUTIONS POSIX 0 Params::ValidationCompiler 0.26 Scalar::Util 0 - Specio 0.18 + Specio 0.50 Specio::Declare 0 Specio::Exporter 0 Specio::Library::Builtins 0 @@ -535,6 +535,45 @@ DISTRIBUTIONS strict 0 warnings 0 warnings::register 0 + DateTime-Format-Builder-0.83 + pathname: D/DR/DROLSKY/DateTime-Format-Builder-0.83.tar.gz + provides: + DateTime::Format::Builder 0.83 + DateTime::Format::Builder::Parser 0.83 + DateTime::Format::Builder::Parser::Dispatch 0.83 + DateTime::Format::Builder::Parser::Quick 0.83 + DateTime::Format::Builder::Parser::Regex 0.83 + DateTime::Format::Builder::Parser::Strptime 0.83 + DateTime::Format::Builder::Parser::generic 0.83 + requirements: + Carp 0 + DateTime 1.00 + DateTime::Format::Strptime 1.04 + ExtUtils::MakeMaker 0 + Params::Validate 0.72 + Scalar::Util 0 + parent 0 + strict 0 + warnings 0 + DateTime-Format-ISO8601-0.17 + pathname: D/DR/DROLSKY/DateTime-Format-ISO8601-0.17.tar.gz + provides: + DateTime::Format::ISO8601 0.17 + DateTime::Format::ISO8601::Types 0.17 + requirements: + Carp 0 + DateTime 1.45 + DateTime::Format::Builder 0.77 + ExtUtils::MakeMaker 0 + Params::ValidationCompiler 0.26 + Specio 0.18 + Specio::Declare 0 + Specio::Exporter 0 + Specio::Library::Builtins 0 + namespace::autoclean 0 + parent 0 + strict 0 + warnings 0 DateTime-Format-Strptime-1.79 pathname: D/DR/DROLSKY/DateTime-Format-Strptime-1.79.tar.gz provides: @@ -560,15 +599,15 @@ DISTRIBUTIONS parent 0 strict 0 warnings 0 - DateTime-Locale-1.44 - pathname: D/DR/DROLSKY/DateTime-Locale-1.44.tar.gz + DateTime-Locale-1.45 + pathname: D/DR/DROLSKY/DateTime-Locale-1.45.tar.gz provides: - DateTime::Locale 1.44 - DateTime::Locale::Base 1.44 - DateTime::Locale::Catalog 1.44 - DateTime::Locale::Data 1.44 - DateTime::Locale::FromData 1.44 - DateTime::Locale::Util 1.44 + DateTime::Locale 1.45 + DateTime::Locale::Base 1.45 + DateTime::Locale::Catalog 1.45 + DateTime::Locale::Data 1.45 + DateTime::Locale::FromData 1.45 + DateTime::Locale::Util 1.45 requirements: Carp 0 Dist::CheckConflicts 0.02 @@ -586,334 +625,335 @@ DISTRIBUTIONS perl 5.008004 strict 0 warnings 0 - DateTime-TimeZone-2.63 - pathname: D/DR/DROLSKY/DateTime-TimeZone-2.63.tar.gz - provides: - DateTime::TimeZone 2.63 - DateTime::TimeZone::Africa::Abidjan 2.63 - DateTime::TimeZone::Africa::Algiers 2.63 - DateTime::TimeZone::Africa::Bissau 2.63 - DateTime::TimeZone::Africa::Cairo 2.63 - DateTime::TimeZone::Africa::Casablanca 2.63 - DateTime::TimeZone::Africa::Ceuta 2.63 - DateTime::TimeZone::Africa::El_Aaiun 2.63 - DateTime::TimeZone::Africa::Johannesburg 2.63 - DateTime::TimeZone::Africa::Juba 2.63 - DateTime::TimeZone::Africa::Khartoum 2.63 - DateTime::TimeZone::Africa::Lagos 2.63 - DateTime::TimeZone::Africa::Maputo 2.63 - DateTime::TimeZone::Africa::Monrovia 2.63 - DateTime::TimeZone::Africa::Nairobi 2.63 - DateTime::TimeZone::Africa::Ndjamena 2.63 - DateTime::TimeZone::Africa::Sao_Tome 2.63 - DateTime::TimeZone::Africa::Tripoli 2.63 - DateTime::TimeZone::Africa::Tunis 2.63 - DateTime::TimeZone::Africa::Windhoek 2.63 - DateTime::TimeZone::America::Adak 2.63 - DateTime::TimeZone::America::Anchorage 2.63 - DateTime::TimeZone::America::Araguaina 2.63 - DateTime::TimeZone::America::Argentina::Buenos_Aires 2.63 - DateTime::TimeZone::America::Argentina::Catamarca 2.63 - DateTime::TimeZone::America::Argentina::Cordoba 2.63 - DateTime::TimeZone::America::Argentina::Jujuy 2.63 - DateTime::TimeZone::America::Argentina::La_Rioja 2.63 - DateTime::TimeZone::America::Argentina::Mendoza 2.63 - DateTime::TimeZone::America::Argentina::Rio_Gallegos 2.63 - DateTime::TimeZone::America::Argentina::Salta 2.63 - DateTime::TimeZone::America::Argentina::San_Juan 2.63 - DateTime::TimeZone::America::Argentina::San_Luis 2.63 - DateTime::TimeZone::America::Argentina::Tucuman 2.63 - DateTime::TimeZone::America::Argentina::Ushuaia 2.63 - DateTime::TimeZone::America::Asuncion 2.63 - DateTime::TimeZone::America::Bahia 2.63 - DateTime::TimeZone::America::Bahia_Banderas 2.63 - DateTime::TimeZone::America::Barbados 2.63 - DateTime::TimeZone::America::Belem 2.63 - DateTime::TimeZone::America::Belize 2.63 - DateTime::TimeZone::America::Boa_Vista 2.63 - DateTime::TimeZone::America::Bogota 2.63 - DateTime::TimeZone::America::Boise 2.63 - DateTime::TimeZone::America::Cambridge_Bay 2.63 - DateTime::TimeZone::America::Campo_Grande 2.63 - DateTime::TimeZone::America::Cancun 2.63 - DateTime::TimeZone::America::Caracas 2.63 - DateTime::TimeZone::America::Cayenne 2.63 - DateTime::TimeZone::America::Chicago 2.63 - DateTime::TimeZone::America::Chihuahua 2.63 - DateTime::TimeZone::America::Ciudad_Juarez 2.63 - DateTime::TimeZone::America::Costa_Rica 2.63 - DateTime::TimeZone::America::Cuiaba 2.63 - DateTime::TimeZone::America::Danmarkshavn 2.63 - DateTime::TimeZone::America::Dawson 2.63 - DateTime::TimeZone::America::Dawson_Creek 2.63 - DateTime::TimeZone::America::Denver 2.63 - DateTime::TimeZone::America::Detroit 2.63 - DateTime::TimeZone::America::Edmonton 2.63 - DateTime::TimeZone::America::Eirunepe 2.63 - DateTime::TimeZone::America::El_Salvador 2.63 - DateTime::TimeZone::America::Fort_Nelson 2.63 - DateTime::TimeZone::America::Fortaleza 2.63 - DateTime::TimeZone::America::Glace_Bay 2.63 - DateTime::TimeZone::America::Goose_Bay 2.63 - DateTime::TimeZone::America::Grand_Turk 2.63 - DateTime::TimeZone::America::Guatemala 2.63 - DateTime::TimeZone::America::Guayaquil 2.63 - DateTime::TimeZone::America::Guyana 2.63 - DateTime::TimeZone::America::Halifax 2.63 - DateTime::TimeZone::America::Havana 2.63 - DateTime::TimeZone::America::Hermosillo 2.63 - DateTime::TimeZone::America::Indiana::Indianapolis 2.63 - DateTime::TimeZone::America::Indiana::Knox 2.63 - DateTime::TimeZone::America::Indiana::Marengo 2.63 - DateTime::TimeZone::America::Indiana::Petersburg 2.63 - DateTime::TimeZone::America::Indiana::Tell_City 2.63 - DateTime::TimeZone::America::Indiana::Vevay 2.63 - DateTime::TimeZone::America::Indiana::Vincennes 2.63 - DateTime::TimeZone::America::Indiana::Winamac 2.63 - DateTime::TimeZone::America::Inuvik 2.63 - DateTime::TimeZone::America::Iqaluit 2.63 - DateTime::TimeZone::America::Jamaica 2.63 - DateTime::TimeZone::America::Juneau 2.63 - DateTime::TimeZone::America::Kentucky::Louisville 2.63 - DateTime::TimeZone::America::Kentucky::Monticello 2.63 - DateTime::TimeZone::America::La_Paz 2.63 - DateTime::TimeZone::America::Lima 2.63 - DateTime::TimeZone::America::Los_Angeles 2.63 - DateTime::TimeZone::America::Maceio 2.63 - DateTime::TimeZone::America::Managua 2.63 - DateTime::TimeZone::America::Manaus 2.63 - DateTime::TimeZone::America::Martinique 2.63 - DateTime::TimeZone::America::Matamoros 2.63 - DateTime::TimeZone::America::Mazatlan 2.63 - DateTime::TimeZone::America::Menominee 2.63 - DateTime::TimeZone::America::Merida 2.63 - DateTime::TimeZone::America::Metlakatla 2.63 - DateTime::TimeZone::America::Mexico_City 2.63 - DateTime::TimeZone::America::Miquelon 2.63 - DateTime::TimeZone::America::Moncton 2.63 - DateTime::TimeZone::America::Monterrey 2.63 - DateTime::TimeZone::America::Montevideo 2.63 - DateTime::TimeZone::America::New_York 2.63 - DateTime::TimeZone::America::Nome 2.63 - DateTime::TimeZone::America::Noronha 2.63 - DateTime::TimeZone::America::North_Dakota::Beulah 2.63 - DateTime::TimeZone::America::North_Dakota::Center 2.63 - DateTime::TimeZone::America::North_Dakota::New_Salem 2.63 - DateTime::TimeZone::America::Nuuk 2.63 - DateTime::TimeZone::America::Ojinaga 2.63 - DateTime::TimeZone::America::Panama 2.63 - DateTime::TimeZone::America::Paramaribo 2.63 - DateTime::TimeZone::America::Phoenix 2.63 - DateTime::TimeZone::America::Port_au_Prince 2.63 - DateTime::TimeZone::America::Porto_Velho 2.63 - DateTime::TimeZone::America::Puerto_Rico 2.63 - DateTime::TimeZone::America::Punta_Arenas 2.63 - DateTime::TimeZone::America::Rankin_Inlet 2.63 - DateTime::TimeZone::America::Recife 2.63 - DateTime::TimeZone::America::Regina 2.63 - DateTime::TimeZone::America::Resolute 2.63 - DateTime::TimeZone::America::Rio_Branco 2.63 - DateTime::TimeZone::America::Santarem 2.63 - DateTime::TimeZone::America::Santiago 2.63 - DateTime::TimeZone::America::Santo_Domingo 2.63 - DateTime::TimeZone::America::Sao_Paulo 2.63 - DateTime::TimeZone::America::Scoresbysund 2.63 - DateTime::TimeZone::America::Sitka 2.63 - DateTime::TimeZone::America::St_Johns 2.63 - DateTime::TimeZone::America::Swift_Current 2.63 - DateTime::TimeZone::America::Tegucigalpa 2.63 - DateTime::TimeZone::America::Thule 2.63 - DateTime::TimeZone::America::Tijuana 2.63 - DateTime::TimeZone::America::Toronto 2.63 - DateTime::TimeZone::America::Vancouver 2.63 - DateTime::TimeZone::America::Whitehorse 2.63 - DateTime::TimeZone::America::Winnipeg 2.63 - DateTime::TimeZone::America::Yakutat 2.63 - DateTime::TimeZone::Antarctica::Casey 2.63 - DateTime::TimeZone::Antarctica::Davis 2.63 - DateTime::TimeZone::Antarctica::Macquarie 2.63 - DateTime::TimeZone::Antarctica::Mawson 2.63 - DateTime::TimeZone::Antarctica::Palmer 2.63 - DateTime::TimeZone::Antarctica::Rothera 2.63 - DateTime::TimeZone::Antarctica::Troll 2.63 - DateTime::TimeZone::Antarctica::Vostok 2.63 - DateTime::TimeZone::Asia::Almaty 2.63 - DateTime::TimeZone::Asia::Amman 2.63 - DateTime::TimeZone::Asia::Anadyr 2.63 - DateTime::TimeZone::Asia::Aqtau 2.63 - DateTime::TimeZone::Asia::Aqtobe 2.63 - DateTime::TimeZone::Asia::Ashgabat 2.63 - DateTime::TimeZone::Asia::Atyrau 2.63 - DateTime::TimeZone::Asia::Baghdad 2.63 - DateTime::TimeZone::Asia::Baku 2.63 - DateTime::TimeZone::Asia::Bangkok 2.63 - DateTime::TimeZone::Asia::Barnaul 2.63 - DateTime::TimeZone::Asia::Beirut 2.63 - DateTime::TimeZone::Asia::Bishkek 2.63 - DateTime::TimeZone::Asia::Chita 2.63 - DateTime::TimeZone::Asia::Colombo 2.63 - DateTime::TimeZone::Asia::Damascus 2.63 - DateTime::TimeZone::Asia::Dhaka 2.63 - DateTime::TimeZone::Asia::Dili 2.63 - DateTime::TimeZone::Asia::Dubai 2.63 - DateTime::TimeZone::Asia::Dushanbe 2.63 - DateTime::TimeZone::Asia::Famagusta 2.63 - DateTime::TimeZone::Asia::Gaza 2.63 - DateTime::TimeZone::Asia::Hebron 2.63 - DateTime::TimeZone::Asia::Ho_Chi_Minh 2.63 - DateTime::TimeZone::Asia::Hong_Kong 2.63 - DateTime::TimeZone::Asia::Hovd 2.63 - DateTime::TimeZone::Asia::Irkutsk 2.63 - DateTime::TimeZone::Asia::Jakarta 2.63 - DateTime::TimeZone::Asia::Jayapura 2.63 - DateTime::TimeZone::Asia::Jerusalem 2.63 - DateTime::TimeZone::Asia::Kabul 2.63 - DateTime::TimeZone::Asia::Kamchatka 2.63 - DateTime::TimeZone::Asia::Karachi 2.63 - DateTime::TimeZone::Asia::Kathmandu 2.63 - DateTime::TimeZone::Asia::Khandyga 2.63 - DateTime::TimeZone::Asia::Kolkata 2.63 - DateTime::TimeZone::Asia::Krasnoyarsk 2.63 - DateTime::TimeZone::Asia::Kuching 2.63 - DateTime::TimeZone::Asia::Macau 2.63 - DateTime::TimeZone::Asia::Magadan 2.63 - DateTime::TimeZone::Asia::Makassar 2.63 - DateTime::TimeZone::Asia::Manila 2.63 - DateTime::TimeZone::Asia::Nicosia 2.63 - DateTime::TimeZone::Asia::Novokuznetsk 2.63 - DateTime::TimeZone::Asia::Novosibirsk 2.63 - DateTime::TimeZone::Asia::Omsk 2.63 - DateTime::TimeZone::Asia::Oral 2.63 - DateTime::TimeZone::Asia::Pontianak 2.63 - DateTime::TimeZone::Asia::Pyongyang 2.63 - DateTime::TimeZone::Asia::Qatar 2.63 - DateTime::TimeZone::Asia::Qostanay 2.63 - DateTime::TimeZone::Asia::Qyzylorda 2.63 - DateTime::TimeZone::Asia::Riyadh 2.63 - DateTime::TimeZone::Asia::Sakhalin 2.63 - DateTime::TimeZone::Asia::Samarkand 2.63 - DateTime::TimeZone::Asia::Seoul 2.63 - DateTime::TimeZone::Asia::Shanghai 2.63 - DateTime::TimeZone::Asia::Singapore 2.63 - DateTime::TimeZone::Asia::Srednekolymsk 2.63 - DateTime::TimeZone::Asia::Taipei 2.63 - DateTime::TimeZone::Asia::Tashkent 2.63 - DateTime::TimeZone::Asia::Tbilisi 2.63 - DateTime::TimeZone::Asia::Tehran 2.63 - DateTime::TimeZone::Asia::Thimphu 2.63 - DateTime::TimeZone::Asia::Tokyo 2.63 - DateTime::TimeZone::Asia::Tomsk 2.63 - DateTime::TimeZone::Asia::Ulaanbaatar 2.63 - DateTime::TimeZone::Asia::Urumqi 2.63 - DateTime::TimeZone::Asia::Ust_Nera 2.63 - DateTime::TimeZone::Asia::Vladivostok 2.63 - DateTime::TimeZone::Asia::Yakutsk 2.63 - DateTime::TimeZone::Asia::Yangon 2.63 - DateTime::TimeZone::Asia::Yekaterinburg 2.63 - DateTime::TimeZone::Asia::Yerevan 2.63 - DateTime::TimeZone::Atlantic::Azores 2.63 - DateTime::TimeZone::Atlantic::Bermuda 2.63 - DateTime::TimeZone::Atlantic::Canary 2.63 - DateTime::TimeZone::Atlantic::Cape_Verde 2.63 - DateTime::TimeZone::Atlantic::Faroe 2.63 - DateTime::TimeZone::Atlantic::Madeira 2.63 - DateTime::TimeZone::Atlantic::South_Georgia 2.63 - DateTime::TimeZone::Atlantic::Stanley 2.63 - DateTime::TimeZone::Australia::Adelaide 2.63 - DateTime::TimeZone::Australia::Brisbane 2.63 - DateTime::TimeZone::Australia::Broken_Hill 2.63 - DateTime::TimeZone::Australia::Darwin 2.63 - DateTime::TimeZone::Australia::Eucla 2.63 - DateTime::TimeZone::Australia::Hobart 2.63 - DateTime::TimeZone::Australia::Lindeman 2.63 - DateTime::TimeZone::Australia::Lord_Howe 2.63 - DateTime::TimeZone::Australia::Melbourne 2.63 - DateTime::TimeZone::Australia::Perth 2.63 - DateTime::TimeZone::Australia::Sydney 2.63 - DateTime::TimeZone::Catalog 2.63 - DateTime::TimeZone::Europe::Andorra 2.63 - DateTime::TimeZone::Europe::Astrakhan 2.63 - DateTime::TimeZone::Europe::Athens 2.63 - DateTime::TimeZone::Europe::Belgrade 2.63 - DateTime::TimeZone::Europe::Berlin 2.63 - DateTime::TimeZone::Europe::Brussels 2.63 - DateTime::TimeZone::Europe::Bucharest 2.63 - DateTime::TimeZone::Europe::Budapest 2.63 - DateTime::TimeZone::Europe::Chisinau 2.63 - DateTime::TimeZone::Europe::Dublin 2.63 - DateTime::TimeZone::Europe::Gibraltar 2.63 - DateTime::TimeZone::Europe::Helsinki 2.63 - DateTime::TimeZone::Europe::Istanbul 2.63 - DateTime::TimeZone::Europe::Kaliningrad 2.63 - DateTime::TimeZone::Europe::Kirov 2.63 - DateTime::TimeZone::Europe::Kyiv 2.63 - DateTime::TimeZone::Europe::Lisbon 2.63 - DateTime::TimeZone::Europe::London 2.63 - DateTime::TimeZone::Europe::Madrid 2.63 - DateTime::TimeZone::Europe::Malta 2.63 - DateTime::TimeZone::Europe::Minsk 2.63 - DateTime::TimeZone::Europe::Moscow 2.63 - DateTime::TimeZone::Europe::Paris 2.63 - DateTime::TimeZone::Europe::Prague 2.63 - DateTime::TimeZone::Europe::Riga 2.63 - DateTime::TimeZone::Europe::Rome 2.63 - DateTime::TimeZone::Europe::Samara 2.63 - DateTime::TimeZone::Europe::Saratov 2.63 - DateTime::TimeZone::Europe::Simferopol 2.63 - DateTime::TimeZone::Europe::Sofia 2.63 - DateTime::TimeZone::Europe::Tallinn 2.63 - DateTime::TimeZone::Europe::Tirane 2.63 - DateTime::TimeZone::Europe::Ulyanovsk 2.63 - DateTime::TimeZone::Europe::Vienna 2.63 - DateTime::TimeZone::Europe::Vilnius 2.63 - DateTime::TimeZone::Europe::Volgograd 2.63 - DateTime::TimeZone::Europe::Warsaw 2.63 - DateTime::TimeZone::Europe::Zurich 2.63 - DateTime::TimeZone::Floating 2.63 - DateTime::TimeZone::Indian::Chagos 2.63 - DateTime::TimeZone::Indian::Maldives 2.63 - DateTime::TimeZone::Indian::Mauritius 2.63 - DateTime::TimeZone::Local 2.63 - DateTime::TimeZone::Local::Android 2.63 - DateTime::TimeZone::Local::Unix 2.63 - DateTime::TimeZone::Local::VMS 2.63 - DateTime::TimeZone::OffsetOnly 2.63 - DateTime::TimeZone::OlsonDB 2.63 - DateTime::TimeZone::OlsonDB::Change 2.63 - DateTime::TimeZone::OlsonDB::Observance 2.63 - DateTime::TimeZone::OlsonDB::Rule 2.63 - DateTime::TimeZone::OlsonDB::Zone 2.63 - DateTime::TimeZone::Pacific::Apia 2.63 - DateTime::TimeZone::Pacific::Auckland 2.63 - DateTime::TimeZone::Pacific::Bougainville 2.63 - DateTime::TimeZone::Pacific::Chatham 2.63 - DateTime::TimeZone::Pacific::Easter 2.63 - DateTime::TimeZone::Pacific::Efate 2.63 - DateTime::TimeZone::Pacific::Fakaofo 2.63 - DateTime::TimeZone::Pacific::Fiji 2.63 - DateTime::TimeZone::Pacific::Galapagos 2.63 - DateTime::TimeZone::Pacific::Gambier 2.63 - DateTime::TimeZone::Pacific::Guadalcanal 2.63 - DateTime::TimeZone::Pacific::Guam 2.63 - DateTime::TimeZone::Pacific::Honolulu 2.63 - DateTime::TimeZone::Pacific::Kanton 2.63 - DateTime::TimeZone::Pacific::Kiritimati 2.63 - DateTime::TimeZone::Pacific::Kosrae 2.63 - DateTime::TimeZone::Pacific::Kwajalein 2.63 - DateTime::TimeZone::Pacific::Marquesas 2.63 - DateTime::TimeZone::Pacific::Nauru 2.63 - DateTime::TimeZone::Pacific::Niue 2.63 - DateTime::TimeZone::Pacific::Norfolk 2.63 - DateTime::TimeZone::Pacific::Noumea 2.63 - DateTime::TimeZone::Pacific::Pago_Pago 2.63 - DateTime::TimeZone::Pacific::Palau 2.63 - DateTime::TimeZone::Pacific::Pitcairn 2.63 - DateTime::TimeZone::Pacific::Port_Moresby 2.63 - DateTime::TimeZone::Pacific::Rarotonga 2.63 - DateTime::TimeZone::Pacific::Tahiti 2.63 - DateTime::TimeZone::Pacific::Tarawa 2.63 - DateTime::TimeZone::Pacific::Tongatapu 2.63 - DateTime::TimeZone::UTC 2.63 + DateTime-TimeZone-2.65 + pathname: D/DR/DROLSKY/DateTime-TimeZone-2.65.tar.gz + provides: + DateTime::TimeZone 2.65 + DateTime::TimeZone::Africa::Abidjan 2.65 + DateTime::TimeZone::Africa::Algiers 2.65 + DateTime::TimeZone::Africa::Bissau 2.65 + DateTime::TimeZone::Africa::Cairo 2.65 + DateTime::TimeZone::Africa::Casablanca 2.65 + DateTime::TimeZone::Africa::Ceuta 2.65 + DateTime::TimeZone::Africa::El_Aaiun 2.65 + DateTime::TimeZone::Africa::Johannesburg 2.65 + DateTime::TimeZone::Africa::Juba 2.65 + DateTime::TimeZone::Africa::Khartoum 2.65 + DateTime::TimeZone::Africa::Lagos 2.65 + DateTime::TimeZone::Africa::Maputo 2.65 + DateTime::TimeZone::Africa::Monrovia 2.65 + DateTime::TimeZone::Africa::Nairobi 2.65 + DateTime::TimeZone::Africa::Ndjamena 2.65 + DateTime::TimeZone::Africa::Sao_Tome 2.65 + DateTime::TimeZone::Africa::Tripoli 2.65 + DateTime::TimeZone::Africa::Tunis 2.65 + DateTime::TimeZone::Africa::Windhoek 2.65 + DateTime::TimeZone::America::Adak 2.65 + DateTime::TimeZone::America::Anchorage 2.65 + DateTime::TimeZone::America::Araguaina 2.65 + DateTime::TimeZone::America::Argentina::Buenos_Aires 2.65 + DateTime::TimeZone::America::Argentina::Catamarca 2.65 + DateTime::TimeZone::America::Argentina::Cordoba 2.65 + DateTime::TimeZone::America::Argentina::Jujuy 2.65 + DateTime::TimeZone::America::Argentina::La_Rioja 2.65 + DateTime::TimeZone::America::Argentina::Mendoza 2.65 + DateTime::TimeZone::America::Argentina::Rio_Gallegos 2.65 + DateTime::TimeZone::America::Argentina::Salta 2.65 + DateTime::TimeZone::America::Argentina::San_Juan 2.65 + DateTime::TimeZone::America::Argentina::San_Luis 2.65 + DateTime::TimeZone::America::Argentina::Tucuman 2.65 + DateTime::TimeZone::America::Argentina::Ushuaia 2.65 + DateTime::TimeZone::America::Asuncion 2.65 + DateTime::TimeZone::America::Bahia 2.65 + DateTime::TimeZone::America::Bahia_Banderas 2.65 + DateTime::TimeZone::America::Barbados 2.65 + DateTime::TimeZone::America::Belem 2.65 + DateTime::TimeZone::America::Belize 2.65 + DateTime::TimeZone::America::Boa_Vista 2.65 + DateTime::TimeZone::America::Bogota 2.65 + DateTime::TimeZone::America::Boise 2.65 + DateTime::TimeZone::America::Cambridge_Bay 2.65 + DateTime::TimeZone::America::Campo_Grande 2.65 + DateTime::TimeZone::America::Cancun 2.65 + DateTime::TimeZone::America::Caracas 2.65 + DateTime::TimeZone::America::Cayenne 2.65 + DateTime::TimeZone::America::Chicago 2.65 + DateTime::TimeZone::America::Chihuahua 2.65 + DateTime::TimeZone::America::Ciudad_Juarez 2.65 + DateTime::TimeZone::America::Costa_Rica 2.65 + DateTime::TimeZone::America::Coyhaique 2.65 + DateTime::TimeZone::America::Cuiaba 2.65 + DateTime::TimeZone::America::Danmarkshavn 2.65 + DateTime::TimeZone::America::Dawson 2.65 + DateTime::TimeZone::America::Dawson_Creek 2.65 + DateTime::TimeZone::America::Denver 2.65 + DateTime::TimeZone::America::Detroit 2.65 + DateTime::TimeZone::America::Edmonton 2.65 + DateTime::TimeZone::America::Eirunepe 2.65 + DateTime::TimeZone::America::El_Salvador 2.65 + DateTime::TimeZone::America::Fort_Nelson 2.65 + DateTime::TimeZone::America::Fortaleza 2.65 + DateTime::TimeZone::America::Glace_Bay 2.65 + DateTime::TimeZone::America::Goose_Bay 2.65 + DateTime::TimeZone::America::Grand_Turk 2.65 + DateTime::TimeZone::America::Guatemala 2.65 + DateTime::TimeZone::America::Guayaquil 2.65 + DateTime::TimeZone::America::Guyana 2.65 + DateTime::TimeZone::America::Halifax 2.65 + DateTime::TimeZone::America::Havana 2.65 + DateTime::TimeZone::America::Hermosillo 2.65 + DateTime::TimeZone::America::Indiana::Indianapolis 2.65 + DateTime::TimeZone::America::Indiana::Knox 2.65 + DateTime::TimeZone::America::Indiana::Marengo 2.65 + DateTime::TimeZone::America::Indiana::Petersburg 2.65 + DateTime::TimeZone::America::Indiana::Tell_City 2.65 + DateTime::TimeZone::America::Indiana::Vevay 2.65 + DateTime::TimeZone::America::Indiana::Vincennes 2.65 + DateTime::TimeZone::America::Indiana::Winamac 2.65 + DateTime::TimeZone::America::Inuvik 2.65 + DateTime::TimeZone::America::Iqaluit 2.65 + DateTime::TimeZone::America::Jamaica 2.65 + DateTime::TimeZone::America::Juneau 2.65 + DateTime::TimeZone::America::Kentucky::Louisville 2.65 + DateTime::TimeZone::America::Kentucky::Monticello 2.65 + DateTime::TimeZone::America::La_Paz 2.65 + DateTime::TimeZone::America::Lima 2.65 + DateTime::TimeZone::America::Los_Angeles 2.65 + DateTime::TimeZone::America::Maceio 2.65 + DateTime::TimeZone::America::Managua 2.65 + DateTime::TimeZone::America::Manaus 2.65 + DateTime::TimeZone::America::Martinique 2.65 + DateTime::TimeZone::America::Matamoros 2.65 + DateTime::TimeZone::America::Mazatlan 2.65 + DateTime::TimeZone::America::Menominee 2.65 + DateTime::TimeZone::America::Merida 2.65 + DateTime::TimeZone::America::Metlakatla 2.65 + DateTime::TimeZone::America::Mexico_City 2.65 + DateTime::TimeZone::America::Miquelon 2.65 + DateTime::TimeZone::America::Moncton 2.65 + DateTime::TimeZone::America::Monterrey 2.65 + DateTime::TimeZone::America::Montevideo 2.65 + DateTime::TimeZone::America::New_York 2.65 + DateTime::TimeZone::America::Nome 2.65 + DateTime::TimeZone::America::Noronha 2.65 + DateTime::TimeZone::America::North_Dakota::Beulah 2.65 + DateTime::TimeZone::America::North_Dakota::Center 2.65 + DateTime::TimeZone::America::North_Dakota::New_Salem 2.65 + DateTime::TimeZone::America::Nuuk 2.65 + DateTime::TimeZone::America::Ojinaga 2.65 + DateTime::TimeZone::America::Panama 2.65 + DateTime::TimeZone::America::Paramaribo 2.65 + DateTime::TimeZone::America::Phoenix 2.65 + DateTime::TimeZone::America::Port_au_Prince 2.65 + DateTime::TimeZone::America::Porto_Velho 2.65 + DateTime::TimeZone::America::Puerto_Rico 2.65 + DateTime::TimeZone::America::Punta_Arenas 2.65 + DateTime::TimeZone::America::Rankin_Inlet 2.65 + DateTime::TimeZone::America::Recife 2.65 + DateTime::TimeZone::America::Regina 2.65 + DateTime::TimeZone::America::Resolute 2.65 + DateTime::TimeZone::America::Rio_Branco 2.65 + DateTime::TimeZone::America::Santarem 2.65 + DateTime::TimeZone::America::Santiago 2.65 + DateTime::TimeZone::America::Santo_Domingo 2.65 + DateTime::TimeZone::America::Sao_Paulo 2.65 + DateTime::TimeZone::America::Scoresbysund 2.65 + DateTime::TimeZone::America::Sitka 2.65 + DateTime::TimeZone::America::St_Johns 2.65 + DateTime::TimeZone::America::Swift_Current 2.65 + DateTime::TimeZone::America::Tegucigalpa 2.65 + DateTime::TimeZone::America::Thule 2.65 + DateTime::TimeZone::America::Tijuana 2.65 + DateTime::TimeZone::America::Toronto 2.65 + DateTime::TimeZone::America::Vancouver 2.65 + DateTime::TimeZone::America::Whitehorse 2.65 + DateTime::TimeZone::America::Winnipeg 2.65 + DateTime::TimeZone::America::Yakutat 2.65 + DateTime::TimeZone::Antarctica::Casey 2.65 + DateTime::TimeZone::Antarctica::Davis 2.65 + DateTime::TimeZone::Antarctica::Macquarie 2.65 + DateTime::TimeZone::Antarctica::Mawson 2.65 + DateTime::TimeZone::Antarctica::Palmer 2.65 + DateTime::TimeZone::Antarctica::Rothera 2.65 + DateTime::TimeZone::Antarctica::Troll 2.65 + DateTime::TimeZone::Antarctica::Vostok 2.65 + DateTime::TimeZone::Asia::Almaty 2.65 + DateTime::TimeZone::Asia::Amman 2.65 + DateTime::TimeZone::Asia::Anadyr 2.65 + DateTime::TimeZone::Asia::Aqtau 2.65 + DateTime::TimeZone::Asia::Aqtobe 2.65 + DateTime::TimeZone::Asia::Ashgabat 2.65 + DateTime::TimeZone::Asia::Atyrau 2.65 + DateTime::TimeZone::Asia::Baghdad 2.65 + DateTime::TimeZone::Asia::Baku 2.65 + DateTime::TimeZone::Asia::Bangkok 2.65 + DateTime::TimeZone::Asia::Barnaul 2.65 + DateTime::TimeZone::Asia::Beirut 2.65 + DateTime::TimeZone::Asia::Bishkek 2.65 + DateTime::TimeZone::Asia::Chita 2.65 + DateTime::TimeZone::Asia::Colombo 2.65 + DateTime::TimeZone::Asia::Damascus 2.65 + DateTime::TimeZone::Asia::Dhaka 2.65 + DateTime::TimeZone::Asia::Dili 2.65 + DateTime::TimeZone::Asia::Dubai 2.65 + DateTime::TimeZone::Asia::Dushanbe 2.65 + DateTime::TimeZone::Asia::Famagusta 2.65 + DateTime::TimeZone::Asia::Gaza 2.65 + DateTime::TimeZone::Asia::Hebron 2.65 + DateTime::TimeZone::Asia::Ho_Chi_Minh 2.65 + DateTime::TimeZone::Asia::Hong_Kong 2.65 + DateTime::TimeZone::Asia::Hovd 2.65 + DateTime::TimeZone::Asia::Irkutsk 2.65 + DateTime::TimeZone::Asia::Jakarta 2.65 + DateTime::TimeZone::Asia::Jayapura 2.65 + DateTime::TimeZone::Asia::Jerusalem 2.65 + DateTime::TimeZone::Asia::Kabul 2.65 + DateTime::TimeZone::Asia::Kamchatka 2.65 + DateTime::TimeZone::Asia::Karachi 2.65 + DateTime::TimeZone::Asia::Kathmandu 2.65 + DateTime::TimeZone::Asia::Khandyga 2.65 + DateTime::TimeZone::Asia::Kolkata 2.65 + DateTime::TimeZone::Asia::Krasnoyarsk 2.65 + DateTime::TimeZone::Asia::Kuching 2.65 + DateTime::TimeZone::Asia::Macau 2.65 + DateTime::TimeZone::Asia::Magadan 2.65 + DateTime::TimeZone::Asia::Makassar 2.65 + DateTime::TimeZone::Asia::Manila 2.65 + DateTime::TimeZone::Asia::Nicosia 2.65 + DateTime::TimeZone::Asia::Novokuznetsk 2.65 + DateTime::TimeZone::Asia::Novosibirsk 2.65 + DateTime::TimeZone::Asia::Omsk 2.65 + DateTime::TimeZone::Asia::Oral 2.65 + DateTime::TimeZone::Asia::Pontianak 2.65 + DateTime::TimeZone::Asia::Pyongyang 2.65 + DateTime::TimeZone::Asia::Qatar 2.65 + DateTime::TimeZone::Asia::Qostanay 2.65 + DateTime::TimeZone::Asia::Qyzylorda 2.65 + DateTime::TimeZone::Asia::Riyadh 2.65 + DateTime::TimeZone::Asia::Sakhalin 2.65 + DateTime::TimeZone::Asia::Samarkand 2.65 + DateTime::TimeZone::Asia::Seoul 2.65 + DateTime::TimeZone::Asia::Shanghai 2.65 + DateTime::TimeZone::Asia::Singapore 2.65 + DateTime::TimeZone::Asia::Srednekolymsk 2.65 + DateTime::TimeZone::Asia::Taipei 2.65 + DateTime::TimeZone::Asia::Tashkent 2.65 + DateTime::TimeZone::Asia::Tbilisi 2.65 + DateTime::TimeZone::Asia::Tehran 2.65 + DateTime::TimeZone::Asia::Thimphu 2.65 + DateTime::TimeZone::Asia::Tokyo 2.65 + DateTime::TimeZone::Asia::Tomsk 2.65 + DateTime::TimeZone::Asia::Ulaanbaatar 2.65 + DateTime::TimeZone::Asia::Urumqi 2.65 + DateTime::TimeZone::Asia::Ust_Nera 2.65 + DateTime::TimeZone::Asia::Vladivostok 2.65 + DateTime::TimeZone::Asia::Yakutsk 2.65 + DateTime::TimeZone::Asia::Yangon 2.65 + DateTime::TimeZone::Asia::Yekaterinburg 2.65 + DateTime::TimeZone::Asia::Yerevan 2.65 + DateTime::TimeZone::Atlantic::Azores 2.65 + DateTime::TimeZone::Atlantic::Bermuda 2.65 + DateTime::TimeZone::Atlantic::Canary 2.65 + DateTime::TimeZone::Atlantic::Cape_Verde 2.65 + DateTime::TimeZone::Atlantic::Faroe 2.65 + DateTime::TimeZone::Atlantic::Madeira 2.65 + DateTime::TimeZone::Atlantic::South_Georgia 2.65 + DateTime::TimeZone::Atlantic::Stanley 2.65 + DateTime::TimeZone::Australia::Adelaide 2.65 + DateTime::TimeZone::Australia::Brisbane 2.65 + DateTime::TimeZone::Australia::Broken_Hill 2.65 + DateTime::TimeZone::Australia::Darwin 2.65 + DateTime::TimeZone::Australia::Eucla 2.65 + DateTime::TimeZone::Australia::Hobart 2.65 + DateTime::TimeZone::Australia::Lindeman 2.65 + DateTime::TimeZone::Australia::Lord_Howe 2.65 + DateTime::TimeZone::Australia::Melbourne 2.65 + DateTime::TimeZone::Australia::Perth 2.65 + DateTime::TimeZone::Australia::Sydney 2.65 + DateTime::TimeZone::Catalog 2.65 + DateTime::TimeZone::Europe::Andorra 2.65 + DateTime::TimeZone::Europe::Astrakhan 2.65 + DateTime::TimeZone::Europe::Athens 2.65 + DateTime::TimeZone::Europe::Belgrade 2.65 + DateTime::TimeZone::Europe::Berlin 2.65 + DateTime::TimeZone::Europe::Brussels 2.65 + DateTime::TimeZone::Europe::Bucharest 2.65 + DateTime::TimeZone::Europe::Budapest 2.65 + DateTime::TimeZone::Europe::Chisinau 2.65 + DateTime::TimeZone::Europe::Dublin 2.65 + DateTime::TimeZone::Europe::Gibraltar 2.65 + DateTime::TimeZone::Europe::Helsinki 2.65 + DateTime::TimeZone::Europe::Istanbul 2.65 + DateTime::TimeZone::Europe::Kaliningrad 2.65 + DateTime::TimeZone::Europe::Kirov 2.65 + DateTime::TimeZone::Europe::Kyiv 2.65 + DateTime::TimeZone::Europe::Lisbon 2.65 + DateTime::TimeZone::Europe::London 2.65 + DateTime::TimeZone::Europe::Madrid 2.65 + DateTime::TimeZone::Europe::Malta 2.65 + DateTime::TimeZone::Europe::Minsk 2.65 + DateTime::TimeZone::Europe::Moscow 2.65 + DateTime::TimeZone::Europe::Paris 2.65 + DateTime::TimeZone::Europe::Prague 2.65 + DateTime::TimeZone::Europe::Riga 2.65 + DateTime::TimeZone::Europe::Rome 2.65 + DateTime::TimeZone::Europe::Samara 2.65 + DateTime::TimeZone::Europe::Saratov 2.65 + DateTime::TimeZone::Europe::Simferopol 2.65 + DateTime::TimeZone::Europe::Sofia 2.65 + DateTime::TimeZone::Europe::Tallinn 2.65 + DateTime::TimeZone::Europe::Tirane 2.65 + DateTime::TimeZone::Europe::Ulyanovsk 2.65 + DateTime::TimeZone::Europe::Vienna 2.65 + DateTime::TimeZone::Europe::Vilnius 2.65 + DateTime::TimeZone::Europe::Volgograd 2.65 + DateTime::TimeZone::Europe::Warsaw 2.65 + DateTime::TimeZone::Europe::Zurich 2.65 + DateTime::TimeZone::Floating 2.65 + DateTime::TimeZone::Indian::Chagos 2.65 + DateTime::TimeZone::Indian::Maldives 2.65 + DateTime::TimeZone::Indian::Mauritius 2.65 + DateTime::TimeZone::Local 2.65 + DateTime::TimeZone::Local::Android 2.65 + DateTime::TimeZone::Local::Unix 2.65 + DateTime::TimeZone::Local::VMS 2.65 + DateTime::TimeZone::OffsetOnly 2.65 + DateTime::TimeZone::OlsonDB 2.65 + DateTime::TimeZone::OlsonDB::Change 2.65 + DateTime::TimeZone::OlsonDB::Observance 2.65 + DateTime::TimeZone::OlsonDB::Rule 2.65 + DateTime::TimeZone::OlsonDB::Zone 2.65 + DateTime::TimeZone::Pacific::Apia 2.65 + DateTime::TimeZone::Pacific::Auckland 2.65 + DateTime::TimeZone::Pacific::Bougainville 2.65 + DateTime::TimeZone::Pacific::Chatham 2.65 + DateTime::TimeZone::Pacific::Easter 2.65 + DateTime::TimeZone::Pacific::Efate 2.65 + DateTime::TimeZone::Pacific::Fakaofo 2.65 + DateTime::TimeZone::Pacific::Fiji 2.65 + DateTime::TimeZone::Pacific::Galapagos 2.65 + DateTime::TimeZone::Pacific::Gambier 2.65 + DateTime::TimeZone::Pacific::Guadalcanal 2.65 + DateTime::TimeZone::Pacific::Guam 2.65 + DateTime::TimeZone::Pacific::Honolulu 2.65 + DateTime::TimeZone::Pacific::Kanton 2.65 + DateTime::TimeZone::Pacific::Kiritimati 2.65 + DateTime::TimeZone::Pacific::Kosrae 2.65 + DateTime::TimeZone::Pacific::Kwajalein 2.65 + DateTime::TimeZone::Pacific::Marquesas 2.65 + DateTime::TimeZone::Pacific::Nauru 2.65 + DateTime::TimeZone::Pacific::Niue 2.65 + DateTime::TimeZone::Pacific::Norfolk 2.65 + DateTime::TimeZone::Pacific::Noumea 2.65 + DateTime::TimeZone::Pacific::Pago_Pago 2.65 + DateTime::TimeZone::Pacific::Palau 2.65 + DateTime::TimeZone::Pacific::Pitcairn 2.65 + DateTime::TimeZone::Pacific::Port_Moresby 2.65 + DateTime::TimeZone::Pacific::Rarotonga 2.65 + DateTime::TimeZone::Pacific::Tahiti 2.65 + DateTime::TimeZone::Pacific::Tarawa 2.65 + DateTime::TimeZone::Pacific::Tongatapu 2.65 + DateTime::TimeZone::UTC 2.65 requirements: Class::Singleton 1.03 Cwd 3 @@ -1130,10 +1170,10 @@ DISTRIBUTIONS perl 5.006 strict 0 warnings 0 - ExtUtils-Depends-0.8001 - pathname: X/XA/XAOC/ExtUtils-Depends-0.8001.tar.gz + ExtUtils-Depends-0.8002 + pathname: E/ET/ETJ/ExtUtils-Depends-0.8002.tar.gz provides: - ExtUtils::Depends 0.8001 + ExtUtils::Depends 0.8002 requirements: Data::Dumper 0 ExtUtils::MakeMaker 7.44 @@ -1443,23 +1483,37 @@ DISTRIBUTIONS Exporter 5.57 ExtUtils::MakeMaker 0 perl 5.008 - IO-Socket-SSL-2.089 - pathname: S/SU/SULLR/IO-Socket-SSL-2.089.tar.gz + IO-Socket-SSL-2.090 + pathname: S/SU/SULLR/IO-Socket-SSL-2.090.tar.gz provides: - IO::Socket::SSL 2.089 + IO::Socket::SSL 2.090 IO::Socket::SSL::Intercept 2.056 - IO::Socket::SSL::OCSP_Cache 2.089 - IO::Socket::SSL::OCSP_Resolver 2.089 + IO::Socket::SSL::OCSP_Cache 2.090 + IO::Socket::SSL::OCSP_Resolver 2.090 IO::Socket::SSL::PublicSuffix undef - IO::Socket::SSL::SSL_Context 2.089 - IO::Socket::SSL::SSL_HANDLE 2.089 - IO::Socket::SSL::Session_Cache 2.089 - IO::Socket::SSL::Trace 2.089 + IO::Socket::SSL::SSL_Context 2.090 + IO::Socket::SSL::SSL_HANDLE 2.090 + IO::Socket::SSL::Session_Cache 2.090 + IO::Socket::SSL::Trace 2.090 IO::Socket::SSL::Utils 2.015 requirements: ExtUtils::MakeMaker 0 Net::SSLeay 1.46 Scalar::Util 0 + IO-Socket-Socks-0.74 + pathname: O/OL/OLEG/IO-Socket-Socks-0.74.tar.gz + provides: + IO::Socket::Socks 0.74 + IO::Socket::Socks::Debug 0.74 + IO::Socket::Socks::Error 0.74 + IO::Socket::Socks::ReadOnlyVar 0.74 + IO::Socket::Socks::SocketClassVar 0.74 + requirements: + ExtUtils::MakeMaker 6.52 + IO::Select 0 + Socket 1.94 + Test::More 0.88 + constant 1.03 IO-String-1.08 pathname: G/GA/GAAS/IO-String-1.08.tar.gz provides: @@ -1647,15 +1701,15 @@ DISTRIBUTIONS Net::Domain 1.05 Net::SMTP 1.28 Test::More 0 - Math-Polygon-1.10 - pathname: M/MA/MARKOV/Math-Polygon-1.10.tar.gz + Math-Polygon-1.11 + pathname: M/MA/MARKOV/Math-Polygon-1.11.tar.gz provides: - Math::Polygon 1.10 - Math::Polygon::Calc 1.10 - Math::Polygon::Clip 1.10 - Math::Polygon::Convex 1.10 - Math::Polygon::Surface 1.10 - Math::Polygon::Transform 1.10 + Math::Polygon 1.11 + Math::Polygon::Calc 1.11 + Math::Polygon::Clip 1.11 + Math::Polygon::Convex 1.11 + Math::Polygon::Surface 1.11 + Math::Polygon::Transform 1.11 requirements: ExtUtils::MakeMaker 0 Math::Trig 0 @@ -1706,10 +1760,10 @@ DISTRIBUTIONS Text::ParseWords 0 perl 5.006001 version 0.87 - Module-Build-Tiny-0.051 - pathname: L/LE/LEONT/Module-Build-Tiny-0.051.tar.gz + Module-Build-Tiny-0.052 + pathname: L/LE/LEONT/Module-Build-Tiny-0.052.tar.gz provides: - Module::Build::Tiny 0.051 + Module::Build::Tiny 0.052 requirements: CPAN::Meta 0 DynaLoader 0 @@ -1742,11 +1796,11 @@ DISTRIBUTIONS Try::Tiny 0 strict 0 warnings 0 - Module-Pluggable-6.2 - pathname: S/SI/SIMONW/Module-Pluggable-6.2.tar.gz + Module-Pluggable-6.3 + pathname: S/SI/SIMONW/Module-Pluggable-6.3.tar.gz provides: Devel::InnerPackage 0.4 - Module::Pluggable 6.2 + Module::Pluggable 6.3 Module::Pluggable::Object 5.2 requirements: Exporter 5.57 @@ -1759,16 +1813,13 @@ DISTRIBUTIONS if 0 perl 5.006 strict 0 - Module-Runtime-0.016 - pathname: Z/ZE/ZEFRAM/Module-Runtime-0.016.tar.gz + Module-Runtime-0.018 + pathname: H/HA/HAARG/Module-Runtime-0.018.tar.gz provides: - Module::Runtime 0.016 + Module::Runtime 0.018 requirements: - Module::Build 0 - Test::More 0.41 - perl 5.006 - strict 0 - warnings 0 + ExtUtils::MakeMaker 0 + perl 5.006000 Mojo-Pg-4.27 pathname: S/SR/SRI/Mojo-Pg-4.27.tar.gz provides: @@ -1784,8 +1835,8 @@ DISTRIBUTIONS Mojolicious 8.50 SQL::Abstract::Pg 1.0 perl 5.016 - Mojolicious-9.39 - pathname: S/SR/SRI/Mojolicious-9.39.tar.gz + Mojolicious-9.40 + pathname: S/SR/SRI/Mojolicious-9.40.tar.gz provides: Mojo undef Mojo::Asset undef @@ -1854,7 +1905,7 @@ DISTRIBUTIONS Mojo::UserAgent::Transactor undef Mojo::Util undef Mojo::WebSocket undef - Mojolicious 9.39 + Mojolicious 9.40 Mojolicious::Command undef Mojolicious::Command::Author::cpanify undef Mojolicious::Command::Author::generate undef @@ -2046,6 +2097,25 @@ DISTRIBUTIONS Scalar::Util 1.18 XSLoader 0.22 parent 0 + Params-Validate-1.31 + pathname: D/DR/DROLSKY/Params-Validate-1.31.tar.gz + provides: + Params::Validate 1.31 + Params::Validate::Constants 1.31 + Params::Validate::PP 1.31 + Params::Validate::XS 1.31 + requirements: + Carp 0 + Exporter 0 + ExtUtils::CBuilder 0 + Module::Build 0.4227 + Module::Implementation 0 + Scalar::Util 1.10 + XSLoader 0 + perl 5.008001 + strict 0 + vars 0 + warnings 0 Params-ValidationCompiler-0.31 pathname: D/DR/DROLSKY/Params-ValidationCompiler-0.31.tar.gz provides: @@ -2064,11 +2134,11 @@ DISTRIBUTIONS overload 0 strict 0 warnings 0 - Path-Tiny-0.146 - pathname: D/DA/DAGOLDEN/Path-Tiny-0.146.tar.gz + Path-Tiny-0.148 + pathname: D/DA/DAGOLDEN/Path-Tiny-0.148.tar.gz provides: - Path::Tiny 0.146 - Path::Tiny::Error 0.146 + Path::Tiny 0.148 + Path::Tiny::Error 0.148 requirements: Carp 0 Cwd 0 @@ -2091,10 +2161,10 @@ DISTRIBUTIONS strict 0 warnings 0 warnings::register 0 - PkgConfig-0.25026 - pathname: P/PL/PLICEASE/PkgConfig-0.25026.tar.gz + PkgConfig-0.26026 + pathname: P/PL/PLICEASE/PkgConfig-0.26026.tar.gz provides: - PkgConfig 0.25026 + PkgConfig 0.26026 requirements: ExtUtils::MakeMaker 6.56 Test::More 0.94 @@ -2142,52 +2212,53 @@ DISTRIBUTIONS ExtUtils::MakeMaker 0 SQL::Abstract 2.0 perl 5.016 - Specio-0.48 - pathname: D/DR/DROLSKY/Specio-0.48.tar.gz - provides: - Specio 0.48 - Specio::Coercion 0.48 - Specio::Constraint::AnyCan 0.48 - Specio::Constraint::AnyDoes 0.48 - Specio::Constraint::AnyIsa 0.48 - Specio::Constraint::Enum 0.48 - Specio::Constraint::Intersection 0.48 - Specio::Constraint::ObjectCan 0.48 - Specio::Constraint::ObjectDoes 0.48 - Specio::Constraint::ObjectIsa 0.48 - Specio::Constraint::Parameterizable 0.48 - Specio::Constraint::Parameterized 0.48 - Specio::Constraint::Role::CanType 0.48 - Specio::Constraint::Role::DoesType 0.48 - Specio::Constraint::Role::Interface 0.48 - Specio::Constraint::Role::IsaType 0.48 - Specio::Constraint::Simple 0.48 - Specio::Constraint::Structurable 0.48 - Specio::Constraint::Structured 0.48 - Specio::Constraint::Union 0.48 - Specio::Declare 0.48 - Specio::DeclaredAt 0.48 - Specio::Exception 0.48 - Specio::Exporter 0.48 - Specio::Helpers 0.48 - Specio::Library::Builtins 0.48 - Specio::Library::Numeric 0.48 - Specio::Library::Perl 0.48 - Specio::Library::String 0.48 - Specio::Library::Structured 0.48 - Specio::Library::Structured::Dict 0.48 - Specio::Library::Structured::Map 0.48 - Specio::Library::Structured::Tuple 0.48 - Specio::OO 0.48 - Specio::PartialDump 0.48 - Specio::Registry 0.48 - Specio::Role::Inlinable 0.48 - Specio::Subs 0.48 - Specio::TypeChecks 0.48 - Test::Specio 0.48 + Specio-0.50 + pathname: D/DR/DROLSKY/Specio-0.50.tar.gz + provides: + Specio 0.50 + Specio::Coercion 0.50 + Specio::Constraint::AnyCan 0.50 + Specio::Constraint::AnyDoes 0.50 + Specio::Constraint::AnyIsa 0.50 + Specio::Constraint::Enum 0.50 + Specio::Constraint::Intersection 0.50 + Specio::Constraint::ObjectCan 0.50 + Specio::Constraint::ObjectDoes 0.50 + Specio::Constraint::ObjectIsa 0.50 + Specio::Constraint::Parameterizable 0.50 + Specio::Constraint::Parameterized 0.50 + Specio::Constraint::Role::CanType 0.50 + Specio::Constraint::Role::DoesType 0.50 + Specio::Constraint::Role::Interface 0.50 + Specio::Constraint::Role::IsaType 0.50 + Specio::Constraint::Simple 0.50 + Specio::Constraint::Structurable 0.50 + Specio::Constraint::Structured 0.50 + Specio::Constraint::Union 0.50 + Specio::Declare 0.50 + Specio::DeclaredAt 0.50 + Specio::Exception 0.50 + Specio::Exporter 0.50 + Specio::Helpers 0.50 + Specio::Library::Builtins 0.50 + Specio::Library::Numeric 0.50 + Specio::Library::Perl 0.50 + Specio::Library::String 0.50 + Specio::Library::Structured 0.50 + Specio::Library::Structured::Dict 0.50 + Specio::Library::Structured::Map 0.50 + Specio::Library::Structured::Tuple 0.50 + Specio::OO 0.50 + Specio::PartialDump 0.50 + Specio::Registry 0.50 + Specio::Role::Inlinable 0.50 + Specio::Subs 0.50 + Specio::TypeChecks 0.50 + Test::Specio 0.50 requirements: B 0 Carp 0 + Clone 0 Devel::StackTrace 0 Eval::Closure 0 Exporter 0 @@ -2199,7 +2270,6 @@ DISTRIBUTIONS Role::Tiny 1.003003 Role::Tiny::With 0 Scalar::Util 0 - Storable 0 Sub::Quote 0 Test::Fatal 0 Test::More 0.96 @@ -2274,60 +2344,60 @@ DISTRIBUTIONS Module::Build 0.38 parent 0.225 perl v5.10.0 - Test-Deep-1.204 - pathname: R/RJ/RJBS/Test-Deep-1.204.tar.gz - provides: - Test::Deep 1.204 - Test::Deep::All 1.204 - Test::Deep::Any 1.204 - Test::Deep::Array 1.204 - Test::Deep::ArrayEach 1.204 - Test::Deep::ArrayElementsOnly 1.204 - Test::Deep::ArrayLength 1.204 - Test::Deep::ArrayLengthOnly 1.204 - Test::Deep::Blessed 1.204 - Test::Deep::Boolean 1.204 - Test::Deep::Cache 1.204 - Test::Deep::Cache::Simple 1.204 - Test::Deep::Class 1.204 - Test::Deep::Cmp 1.204 - Test::Deep::Code 1.204 - Test::Deep::Hash 1.204 - Test::Deep::HashEach 1.204 - Test::Deep::HashElements 1.204 - Test::Deep::HashKeys 1.204 - Test::Deep::HashKeysOnly 1.204 - Test::Deep::Ignore 1.204 - Test::Deep::Isa 1.204 - Test::Deep::ListMethods 1.204 - Test::Deep::MM 1.204 - Test::Deep::Methods 1.204 - Test::Deep::NoTest 1.204 - Test::Deep::None 1.204 - Test::Deep::Number 1.204 - Test::Deep::Obj 1.204 - Test::Deep::Ref 1.204 - Test::Deep::RefType 1.204 - Test::Deep::Regexp 1.204 - Test::Deep::RegexpMatches 1.204 - Test::Deep::RegexpOnly 1.204 - Test::Deep::RegexpRef 1.204 - Test::Deep::RegexpRefOnly 1.204 - Test::Deep::RegexpVersion 1.204 - Test::Deep::ScalarRef 1.204 - Test::Deep::ScalarRefOnly 1.204 - Test::Deep::Set 1.204 - Test::Deep::Shallow 1.204 - Test::Deep::Stack 1.204 - Test::Deep::String 1.204 - Test::Deep::SubHash 1.204 - Test::Deep::SubHashElements 1.204 - Test::Deep::SubHashKeys 1.204 - Test::Deep::SubHashKeysOnly 1.204 - Test::Deep::SuperHash 1.204 - Test::Deep::SuperHashElements 1.204 - Test::Deep::SuperHashKeys 1.204 - Test::Deep::SuperHashKeysOnly 1.204 + Test-Deep-1.205 + pathname: R/RJ/RJBS/Test-Deep-1.205.tar.gz + provides: + Test::Deep 1.205 + Test::Deep::All 1.205 + Test::Deep::Any 1.205 + Test::Deep::Array 1.205 + Test::Deep::ArrayEach 1.205 + Test::Deep::ArrayElementsOnly 1.205 + Test::Deep::ArrayLength 1.205 + Test::Deep::ArrayLengthOnly 1.205 + Test::Deep::Blessed 1.205 + Test::Deep::Boolean 1.205 + Test::Deep::Cache 1.205 + Test::Deep::Cache::Simple 1.205 + Test::Deep::Class 1.205 + Test::Deep::Cmp 1.205 + Test::Deep::Code 1.205 + Test::Deep::Hash 1.205 + Test::Deep::HashEach 1.205 + Test::Deep::HashElements 1.205 + Test::Deep::HashKeys 1.205 + Test::Deep::HashKeysOnly 1.205 + Test::Deep::Ignore 1.205 + Test::Deep::Isa 1.205 + Test::Deep::ListMethods 1.205 + Test::Deep::MM 1.205 + Test::Deep::Methods 1.205 + Test::Deep::NoTest 1.205 + Test::Deep::None 1.205 + Test::Deep::Number 1.205 + Test::Deep::Obj 1.205 + Test::Deep::Ref 1.205 + Test::Deep::RefType 1.205 + Test::Deep::Regexp 1.205 + Test::Deep::RegexpMatches 1.205 + Test::Deep::RegexpOnly 1.205 + Test::Deep::RegexpRef 1.205 + Test::Deep::RegexpRefOnly 1.205 + Test::Deep::RegexpVersion 1.205 + Test::Deep::ScalarRef 1.205 + Test::Deep::ScalarRefOnly 1.205 + Test::Deep::Set 1.205 + Test::Deep::Shallow 1.205 + Test::Deep::Stack 1.205 + Test::Deep::String 1.205 + Test::Deep::SubHash 1.205 + Test::Deep::SubHashElements 1.205 + Test::Deep::SubHashKeys 1.205 + Test::Deep::SubHashKeysOnly 1.205 + Test::Deep::SuperHash 1.205 + Test::Deep::SuperHashElements 1.205 + Test::Deep::SuperHashKeys 1.205 + Test::Deep::SuperHashKeysOnly 1.205 requirements: ExtUtils::MakeMaker 6.78 List::Util 1.09 @@ -2397,12 +2467,12 @@ DISTRIBUTIONS Test::Builder::Tester 1.02 Test::More 0.62 perl 5.008 - Text-CSV-2.04 - pathname: I/IS/ISHIGAKI/Text-CSV-2.04.tar.gz + Text-CSV-2.06 + pathname: I/IS/ISHIGAKI/Text-CSV-2.06.tar.gz provides: - Text::CSV 2.04 - Text::CSV::ErrorDiag 2.04 - Text::CSV_PP 2.04 + Text::CSV 2.06 + Text::CSV::ErrorDiag 2.06 + Text::CSV_PP 2.06 requirements: ExtUtils::MakeMaker 0 IO::Handle 0 @@ -2532,39 +2602,44 @@ DISTRIBUTIONS TimeDate 1.21 requirements: ExtUtils::MakeMaker 0 - Travel-Status-DE-DBWagenreihung-0.18 - pathname: D/DE/DERF/Travel-Status-DE-DBWagenreihung-0.18.tar.gz + Travel-Status-DE-DBRIS-0.11 + pathname: D/DE/DERF/Travel-Status-DE-DBRIS-0.11.tar.gz provides: - Travel::Status::DE::DBWagenreihung 0.18 - Travel::Status::DE::DBWagenreihung::Carriage 0.18 - Travel::Status::DE::DBWagenreihung::Group 0.18 - Travel::Status::DE::DBWagenreihung::Sector 0.18 + Travel::Status::DE::DBRIS 0.11 + Travel::Status::DE::DBRIS::Formation 0.11 + Travel::Status::DE::DBRIS::Formation::Carriage 0.11 + Travel::Status::DE::DBRIS::Formation::Group 0.11 + Travel::Status::DE::DBRIS::Formation::Sector 0.11 + Travel::Status::DE::DBRIS::Journey 0.11 + Travel::Status::DE::DBRIS::JourneyAtStop 0.11 + Travel::Status::DE::DBRIS::Location 0.11 requirements: Carp 0 - Class::Accessor 0 + Class::Accessor 0.16 + DateTime 0 + DateTime::Format::Strptime 0 Getopt::Long 0 JSON 0 + LWP::Protocol::https 0 LWP::UserAgent 0 List::Util 0 Module::Build 0.4 Test::Compile 0 Test::More 0 Test::Pod 0 - Travel::Status::DE::IRIS 1.2 perl v5.20.0 - Travel-Status-DE-DeutscheBahn-6.15 - pathname: D/DE/DERF/Travel-Status-DE-DeutscheBahn-6.15.tar.gz - provides: - Travel::Status::DE::DeutscheBahn 6.15 - Travel::Status::DE::HAFAS 6.15 - Travel::Status::DE::HAFAS::Journey 6.15 - Travel::Status::DE::HAFAS::Location 6.15 - Travel::Status::DE::HAFAS::Message 6.15 - Travel::Status::DE::HAFAS::Polyline 6.15 - Travel::Status::DE::HAFAS::Product 6.15 - Travel::Status::DE::HAFAS::Services 6.15 - Travel::Status::DE::HAFAS::Stop 6.15 - Travel::Status::DE::HAFAS::StopFinder 6.15 + Travel-Status-DE-HAFAS-6.20 + pathname: D/DE/DERF/Travel-Status-DE-HAFAS-6.20.tar.gz + provides: + Travel::Status::DE::HAFAS 6.20 + Travel::Status::DE::HAFAS::Journey 6.20 + Travel::Status::DE::HAFAS::Location 6.20 + Travel::Status::DE::HAFAS::Message 6.20 + Travel::Status::DE::HAFAS::Polyline 6.20 + Travel::Status::DE::HAFAS::Product 6.20 + Travel::Status::DE::HAFAS::Services 6.20 + Travel::Status::DE::HAFAS::Stop 6.20 + Travel::Status::DE::HAFAS::StopFinder 6.20 requirements: Carp 0 Class::Accessor 0.16 @@ -2614,6 +2689,32 @@ DISTRIBUTIONS Text::LevenshteinXS 0 XML::LibXML 0 perl v5.14.2 + Travel-Status-MOTIS-0.01 + pathname: D/DE/DERF/Travel-Status-MOTIS-0.01.tar.gz + provides: + Travel::Status::MOTIS 0.01 + Travel::Status::MOTIS::Polyline 0.01 + Travel::Status::MOTIS::Services 0.01 + Travel::Status::MOTIS::Stop 0.01 + Travel::Status::MOTIS::Stopover 0.01 + Travel::Status::MOTIS::Trip 0.01 + Travel::Status::MOTIS::TripAtStopover 0.01 + requirements: + Carp 0 + Class::Accessor 0.16 + DateTime 0 + DateTime::Format::ISO8601 0 + Getopt::Long 0 + JSON 0 + LWP::Protocol::https 0 + LWP::UserAgent 0 + List::Util 0 + Module::Build 0.4 + Test::Compile 0 + Test::More 0 + Test::Pod 0 + URI 0 + perl v5.20.0 Try-Tiny-0.32 pathname: E/ET/ETHER/Try-Tiny-0.32.tar.gz provides: @@ -2636,62 +2737,63 @@ DISTRIBUTIONS requirements: ExtUtils::MakeMaker 0 common::sense 0 - URI-5.31 - pathname: O/OA/OALDERS/URI-5.31.tar.gz - provides: - URI 5.31 - URI::Escape 5.31 - URI::Heuristic 5.31 - URI::IRI 5.31 - URI::QueryParam 5.31 - URI::Split 5.31 - URI::URL 5.31 - URI::WithBase 5.31 - URI::data 5.31 - URI::file 5.31 - URI::file::Base 5.31 - URI::file::FAT 5.31 - URI::file::Mac 5.31 - URI::file::OS2 5.31 - URI::file::QNX 5.31 - URI::file::Unix 5.31 - URI::file::Win32 5.31 - URI::ftp 5.31 - URI::ftpes 5.31 - URI::ftps 5.31 - URI::geo 5.31 - URI::gopher 5.31 - URI::http 5.31 - URI::https 5.31 - URI::icap 5.31 - URI::icaps 5.31 - URI::irc 5.31 - URI::ircs 5.31 - URI::ldap 5.31 - URI::ldapi 5.31 - URI::ldaps 5.31 - URI::mailto 5.31 - URI::mms 5.31 - URI::news 5.31 - URI::nntp 5.31 - URI::nntps 5.31 - URI::otpauth 5.31 - URI::pop 5.31 - URI::rlogin 5.31 - URI::rsync 5.31 - URI::rtsp 5.31 - URI::rtspu 5.31 - URI::scp 5.31 - URI::sftp 5.31 - URI::sip 5.31 - URI::sips 5.31 - URI::snews 5.31 - URI::ssh 5.31 - URI::telnet 5.31 - URI::tn3270 5.31 - URI::urn 5.31 - URI::urn::isbn 5.31 - URI::urn::oid 5.31 + URI-5.32 + pathname: O/OA/OALDERS/URI-5.32.tar.gz + provides: + URI 5.32 + URI::Escape 5.32 + URI::Heuristic 5.32 + URI::IRI 5.32 + URI::QueryParam 5.32 + URI::Split 5.32 + URI::URL 5.32 + URI::WithBase 5.32 + URI::data 5.32 + URI::file 5.32 + URI::file::Base 5.32 + URI::file::FAT 5.32 + URI::file::Mac 5.32 + URI::file::OS2 5.32 + URI::file::QNX 5.32 + URI::file::Unix 5.32 + URI::file::Win32 5.32 + URI::ftp 5.32 + URI::ftpes 5.32 + URI::ftps 5.32 + URI::geo 5.32 + URI::gopher 5.32 + URI::http 5.32 + URI::https 5.32 + URI::icap 5.32 + URI::icaps 5.32 + URI::irc 5.32 + URI::ircs 5.32 + URI::ldap 5.32 + URI::ldapi 5.32 + URI::ldaps 5.32 + URI::mailto 5.32 + URI::mms 5.32 + URI::news 5.32 + URI::nntp 5.32 + URI::nntps 5.32 + URI::otpauth 5.32 + URI::pop 5.32 + URI::rlogin 5.32 + URI::rsync 5.32 + URI::rtsp 5.32 + URI::rtspu 5.32 + URI::scp 5.32 + URI::sftp 5.32 + URI::sip 5.32 + URI::sips 5.32 + URI::smb 5.32 + URI::snews 5.32 + URI::ssh 5.32 + URI::telnet 5.32 + URI::tn3270 5.32 + URI::urn 5.32 + URI::urn::isbn 5.32 + URI::urn::oid 5.32 requirements: Carp 0 Cwd 0 @@ -2903,32 +3005,32 @@ DISTRIBUTIONS XSLoader 0 lib 0 perl 5.008001 - libwww-perl-6.77 - pathname: O/OA/OALDERS/libwww-perl-6.77.tar.gz - provides: - LWP 6.77 - LWP::Authen::Basic 6.77 - LWP::Authen::Digest 6.77 - LWP::Authen::Ntlm 6.77 - LWP::ConnCache 6.77 - LWP::Debug 6.77 - LWP::Debug::TraceHTTP 6.77 - LWP::DebugFile 6.77 - LWP::MemberMixin 6.77 - LWP::Protocol 6.77 - LWP::Protocol::cpan 6.77 - LWP::Protocol::data 6.77 - LWP::Protocol::file 6.77 - LWP::Protocol::ftp 6.77 - LWP::Protocol::gopher 6.77 - LWP::Protocol::http 6.77 - LWP::Protocol::loopback 6.77 - LWP::Protocol::mailto 6.77 - LWP::Protocol::nntp 6.77 - LWP::Protocol::nogo 6.77 - LWP::RobotUA 6.77 - LWP::Simple 6.77 - LWP::UserAgent 6.77 + libwww-perl-6.78 + pathname: O/OA/OALDERS/libwww-perl-6.78.tar.gz + provides: + LWP 6.78 + LWP::Authen::Basic 6.78 + LWP::Authen::Digest 6.78 + LWP::Authen::Ntlm 6.78 + LWP::ConnCache 6.78 + LWP::Debug 6.78 + LWP::Debug::TraceHTTP 6.78 + LWP::DebugFile 6.78 + LWP::MemberMixin 6.78 + LWP::Protocol 6.78 + LWP::Protocol::cpan 6.78 + LWP::Protocol::data 6.78 + LWP::Protocol::file 6.78 + LWP::Protocol::ftp 6.78 + LWP::Protocol::gopher 6.78 + LWP::Protocol::http 6.78 + LWP::Protocol::loopback 6.78 + LWP::Protocol::mailto 6.78 + LWP::Protocol::nntp 6.78 + LWP::Protocol::nogo 6.78 + LWP::RobotUA 6.78 + LWP::Simple 6.78 + LWP::UserAgent 6.78 requirements: Digest::MD5 0 Encode 2.12 diff --git a/examples/travelynx.conf b/examples/travelynx.conf index f8eaac0..fdcd03e 100644 --- a/examples/travelynx.conf +++ b/examples/travelynx.conf @@ -35,6 +35,34 @@ password => die("Changeme!"), }, + # Settings specific to the DBRIS bahn.de backend. + # Their journey endpoint (which is required for checkins) is behind an IP + # reputation filter, denying requests from most non-residential IP ranges. + # If needed, you can specify either a single SOCKS proxy or a set of + # SOCKS proxies here, and thus work around that limitation. If multiple + # proxies are specified, travelynx will choose a random one for each + # request. Note that DBRIS bahn.de requests to non-journey endpoints + # (such as the departure board) are always sent directly and not passed + # through the proxy / proxies specified here. + # "proxies" takes precedence over "proxy". + dbris => { + 'bahn.de' => { + # proxy => 'socks://127.0.0.1:18080', # <- either this + # proxies => ['socks://127.0.0.1:18080', 'socks://127.0.0.1:18081'], + }, + }, + + # Settings specific to HAFAS backends. + # For instance, the PKP backend is hidden behind a GeoIP filter, hence + # travelynx only supports it if travelynx.conf either indicates that it + # is reachable or specifies a proxy. + hafas => { + PKP => { + # geoip_ok => 1, # <- either this + # proxy => 'socks://...', # <- or this + }, + }, + # These settings control the amount and (re)spawn behaviour of travelynx # worker processes as well as IP, port, and PID file. They are suitable for # up to a few dozen concurrent users. If your site has more traffic, you diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 0c8ceec..75c9b57 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -1,6 +1,7 @@ package Travelynx; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -19,11 +20,13 @@ use JSON; use List::Util; use List::UtilsBy qw(uniq_by); use List::MoreUtils qw(first_index); -use Travel::Status::DE::DBWagenreihung; +use Travel::Status::DE::DBRIS::Formation; use Travelynx::Helper::DBDB; +use Travelynx::Helper::DBRIS; use Travelynx::Helper::EFA; use Travelynx::Helper::HAFAS; use Travelynx::Helper::IRIS; +use Travelynx::Helper::MOTIS; use Travelynx::Helper::Sendmail; use Travelynx::Helper::Traewelling; use Travelynx::Model::InTransit; @@ -185,7 +188,7 @@ sub startup { $self->attr( ice_name => sub { state $id_to_name = { - Travel::Status::DE::DBWagenreihung::Group::name_to_designation( + Travel::Status::DE::DBRIS::Formation::Group::name_to_designation( ) }; return $id_to_name; @@ -233,12 +236,26 @@ sub startup { } ); + $self->helper( + dbris => sub { + my ($self) = @_; + state $dbris = Travelynx::Helper::DBRIS->new( + log => $self->app->log, + service_config => $self->app->config->{dbris}, + cache => $self->app->cache_iris_rt, + root_url => $self->base_url_for('/')->to_abs, + user_agent => $self->ua, + version => $self->app->config->{version}, + ); + } + ); $self->helper( hafas => sub { my ($self) = @_; state $hafas = Travelynx::Helper::HAFAS->new( log => $self->app->log, + service_config => $self->app->config->{hafas}, main_cache => $self->app->cache_iris_main, realtime_cache => $self->app->cache_iris_rt, root_url => $self->base_url_for('/')->to_abs, @@ -262,6 +279,19 @@ sub startup { ); $self->helper( + motis => sub { + my ($self) = @_; + state $motis = Travelynx::Helper::MOTIS->new( + log => $self->app->log, + cache => $self->app->cache_iris_rt, + user_agent => $self->ua, + root_url => $self->base_url_for('/')->to_abs, + version => $self->app->config->{version}, + ); + } + ); + + $self->helper( traewelling => sub { my ($self) = @_; state $trwl = Travelynx::Model::Traewelling->new( pg => $self->pg ); @@ -422,6 +452,14 @@ sub startup { my $first = $load->{FIRST} // 0; my $second = $load->{SECOND} // 0; + # DBRIS + if ( $first == 99 ) { + $first = 4; + } + if ( $second == 99 ) { + $second = 4; + } + my @symbols = ( qw(help_outline person_outline people priority_high not_interested) @@ -469,6 +507,12 @@ sub startup { return Mojo::Promise->reject('You are already checked in'); } + if ( $opt{motis} ) { + return $self->_checkin_motis_p(%opt); + } + if ( $opt{dbris} ) { + return $self->_checkin_dbris_p(%opt); + } if ( $opt{hafas} ) { return $self->_checkin_hafas_p(%opt); } @@ -548,6 +592,300 @@ sub startup { ); $self->helper( + '_checkin_motis_p' => sub { + my ( $self, %opt ) = @_; + + my $station = $opt{station}; + my $train_id = $opt{train_id}; + my $ts = $opt{ts}; + my $uid = $opt{uid} // $self->current_user->{id}; + my $db = $opt{db} // $self->pg->db; + my $hafas; + + my $promise = Mojo::Promise->new; + + $self->motis->get_trip_p( + service => $opt{motis}, + trip_id => $train_id, + )->then( + sub { + my ($trip) = @_; + my $found_stopover; + + for my $stopover ( $trip->stopovers ) { + if ( $stopover->stop->id eq $station ) { + $found_stopover = $stopover; + + # Lines may serve the same stop several times. + # Keep looking until the scheduled departure + # matches the one passed while checking in. + if ( $ts + and $stopover->scheduled_departure->epoch + == $ts ) + { + last; + } + } + } + + if ( not $found_stopover ) { + $promise->reject( +"Did not find stopover at '$station' within trip '$train_id'" + ); + return; + } + + for my $stopover ( $trip->stopovers ) { + $self->stations->add_or_update( + stop => $stopover->stop, + db => $db, + motis => $opt{motis}, + ); + } + + $self->stations->add_or_update( + stop => $found_stopover->stop, + db => $db, + motis => $opt{motis}, + ); + + eval { + $self->in_transit->add( + uid => $uid, + db => $db, + journey => $trip, + stopover => $found_stopover, + data => { trip_id => $train_id }, + backend_id => $self->stations->get_backend_id( + motis => $opt{motis} + ), + ); + }; + + if ($@) { + $self->app->log->error( + "Checkin($uid): INSERT failed: $@"); + $promise->reject( 'INSERT failed: ' . $@ ); + return; + } + + my $polyline; + if ( $trip->polyline ) { + my @station_list; + my @coordinate_list; + for my $coordinate ( $trip->polyline ) { + if ( $coordinate->{stop} ) { + if ( not defined $coordinate->{stop}->{eva} ) { + die(); + } + + push( + @coordinate_list, + [ + $coordinate->{lon}, + $coordinate->{lat}, + $coordinate->{stop}->{eva} + ] + ); + + push( @station_list, + $coordinate->{stop}->name ); + } + else { + push( @coordinate_list, + [ $coordinate->{lon}, $coordinate->{lat} ] + ); + } + } + + # equal length → polyline only consists of straight + # lines between stops. that's not helpful. + if ( @station_list == @coordinate_list ) { + $self->log->debug( 'Ignoring polyline for ' + . $trip->route_name + . ' as it only consists of straight lines between stops.' + ); + } + else { + $polyline = { + from_eva => + ( $trip->stopovers )[0]->stop->{eva}, + to_eva => ( $trip->stopovers )[-1]->stop->{eva}, + coords => \@coordinate_list, + }; + } + } + + if ($polyline) { + $self->in_transit->set_polyline( + uid => $uid, + db => $db, + polyline => $polyline, + ); + } + + # mustn't be called during a transaction + if ( not $opt{in_transaction} ) { + $self->run_hook( $uid, 'checkin' ); + } + + $promise->resolve($trip); + } + )->catch( + sub { + my ($err) = @_; + $promise->reject($err); + return; + } + )->wait; + + return $promise; + } + ); + + $self->helper( + '_checkin_dbris_p' => sub { + my ( $self, %opt ) = @_; + + my $station = $opt{station}; + my $train_id = $opt{train_id}; + my $train_suffix = $opt{train_suffix}; + my $ts = $opt{ts}; + my $uid = $opt{uid} // $self->current_user->{id}; + my $db = $opt{db} // $self->pg->db; + my $hafas; + + my $promise = Mojo::Promise->new; + + $self->dbris->get_journey_p( + trip_id => $train_id, + with_polyline => 1 + )->then( + sub { + my ($journey) = @_; + my $found; + for my $stop ( $journey->route ) { + if ( $stop->eva eq $station ) { + $found = $stop; + + # Lines may serve the same stop several times. + # Keep looking until the scheduled departure + # matches the one passed while checking in. + if ( $ts and $stop->sched_dep->epoch == $ts ) { + last; + } + } + } + if ( not $found ) { + $promise->reject( +"Did not find stop '$station' within journey '$train_id'" + ); + return; + } + for my $stop ( $journey->route ) { + $self->stations->add_or_update( + stop => $stop, + db => $db, + dbris => 'bahn.de', + ); + } + eval { + $self->in_transit->add( + uid => $uid, + db => $db, + journey => $journey, + stop => $found, + data => { trip_id => $train_id }, + backend_id => $self->stations->get_backend_id( + dbris => 'bahn.de' + ), + train_suffix => $train_suffix, + ); + }; + if ($@) { + $self->app->log->error( + "Checkin($uid): INSERT failed: $@"); + $promise->reject( 'INSERT failed: ' . $@ ); + return; + } + + my $polyline; + if ( $journey->polyline ) { + my @station_list; + my @coordinate_list; + for my $coord ( $journey->polyline ) { + if ( $coord->{stop} ) { + push( + @coordinate_list, + [ + $coord->{lon}, $coord->{lat}, + $coord->{stop}->eva + ] + ); + push( @station_list, $coord->{stop}->name ); + } + else { + push( @coordinate_list, + [ $coord->{lon}, $coord->{lat} ] ); + } + } + + # equal length → polyline only consists of straight + # lines between stops. that's not helpful. + if ( @station_list == @coordinate_list ) { + $self->log->debug( 'Ignoring polyline for ' + . $journey->train + . ' as it only consists of straight lines between stops.' + ); + } + else { + $polyline = { + from_eva => ( $journey->route )[0]->eva, + to_eva => ( $journey->route )[-1]->eva, + coords => \@coordinate_list, + }; + } + } + + if ($polyline) { + $self->in_transit->set_polyline( + uid => $uid, + db => $db, + polyline => $polyline, + ); + } + + # mustn't be called during a transaction + if ( not $opt{in_transaction} ) { + $self->run_hook( $uid, 'checkin' ); + $self->add_wagonorder( + uid => $uid, + train_id => $train_id, + is_departure => 1, + eva => $found->eva, + datetime => $found->sched_dep, + train_type => $journey->type, + train_no => $journey->train_no, + ); + $self->add_stationinfo( $uid, 1, $train_id, + $found->eva ); + } + + $promise->resolve($journey); + } + )->catch( + sub { + my ($err) = @_; + $promise->reject($err); + return; + } + )->wait; + + return $promise; + } + ); + + $self->helper( '_checkin_hafas_p' => sub { my ( $self, %opt ) = @_; @@ -805,8 +1143,7 @@ sub startup { } if ( not $user->{checked_in} and not $user->{cancelled} ) { - return $promise->resolve( 0, - 'You are not checked into any train' ); + return $promise->resolve( 0, 'You are not checked in' ); } if ( $dep_eva and $dep_eva != $user->{dep_eva} ) { @@ -816,8 +1153,8 @@ sub startup { return $promise->resolve( 0, 'race condition' ); } - if ( $user->{is_hafas} ) { - return $self->_checkout_hafas_p(%opt); + if ( $user->{is_dbris} or $user->{is_hafas} or $user->{is_motis} ) { + return $self->_checkout_journey_p(%opt); } my $now = DateTime->now( time_zone => 'Europe/Berlin' ); @@ -950,7 +1287,7 @@ sub startup { if (@unknown_stations) { $self->app->log->warn( sprintf( -'Route of %s %s (%s -> %s) contains unknown stations: %s', +'IRIS: Route of %s %s (%s -> %s) contains unknown stations: %s', $train->type, $train->train_no, $train->origin, @@ -1066,7 +1403,7 @@ sub startup { ); $self->helper( - '_checkout_hafas_p' => sub { + '_checkout_journey_p' => sub { my ( $self, %opt ) = @_; my $station = $opt{station}; @@ -1092,7 +1429,7 @@ sub startup { my $has_arrived; for my $stop ( @{ $journey->{route_after} } ) { if ( $station eq $stop->[0] or $station eq $stop->[1] ) { - $found = 1; + $found = $stop; $self->in_transit->set_arrival_eva( uid => $uid, db => $db, @@ -1160,6 +1497,22 @@ sub startup { uid => $uid ); } + elsif ( $found and $found->[2]{isCancelled} ) { + $journey = $self->in_transit->get( + uid => $uid, + db => $db + ); + $journey->{cancelled} = 1; + $self->journeys->add_from_in_transit( + db => $db, + journey => $journey + ); + $self->in_transit->set_cancelled_destination( + uid => $uid, + db => $db, + cancelled_destination => $found->[0], + ); + } if ($tx) { $tx->commit; @@ -1301,35 +1654,52 @@ sub startup { my $data = {}; my $user_data = {}; - if ( $opt{is_departure} + my $wr; + eval { + $wr + = Travel::Status::DE::DBRIS::Formation->new( + json => $wagonorder ); + }; + + if ( $opt{is_departure} + and $wr and not exists $wagonorder->{error} ) { + my $dt + = $opt{datetime}->clone->set_time_zone('UTC'); $data->{wagonorder_dep} = $wagonorder; + $data->{wagonorder_param} = { + time => $dt->rfc3339 =~ s{(?=Z)}{.000}r, + number => $opt{train_no}, + evaNumber => $opt{eva}, + administrationId => 80, + date => $dt->strftime('%Y-%m-%d'), + category => $opt{train_type}, + }; $user_data->{wagongroups} = []; - for my $group ( @{ $wagonorder->{groups} // [] } ) { + for my $group ( $wr->groups ) { my @wagons; - for my $wagon ( @{ $group->{vehicles} // [] } ) - { + for my $wagon ( $group->carriages ) { push( @wagons, { - id => $wagon->{vehicleID}, - number => $wagon - ->{wagonIdentificationNumber}, - type => - $wagon->{type}{constructionType}, + id => $wagon->uic_id, + number => $wagon->number, + type => $wagon->type, } ); } push( @{ $user_data->{wagongroups} }, { - name => $group->{name}, - to => $group->{transport}{destination} - {name}, - type => $group->{transport}{category}, - no => $group->{transport}{number}, - wagons => [@wagons], + name => $group->name, + desc => $group->desc_short, + description => $group->description, + designation => $group->designation, + to => $group->destination, + type => $group->train_type, + no => $group->train_no, + wagons => [@wagons], } ); if ( $group->{name} @@ -1608,7 +1978,15 @@ sub startup { $ret =~ s{[{]tt[}]}{$opt{tt}}g; $ret =~ s{[{]tn[}]}{$opt{tn}}g; $ret =~ s{[{]id[}]}{$opt{id}}g; + $ret =~ s{[{]dbris[}]}{$opt{dbris}}g; $ret =~ s{[{]hafas[}]}{$opt{hafas}}g; + + if ( $opt{id} and not $opt{is_iris} ) { + $ret =~ s{[{]id_or_tttn[}]}{$opt{id}}g; + } + else { + $ret =~ s{[{]id_or_tttn[}]}{$opt{tt}$opt{tn}}g; + } return $ret; } ); @@ -1639,8 +2017,8 @@ sub startup { my $wr; eval { $wr - = Travel::Status::DE::DBWagenreihung->new( - from_json => $wagonorder ); + = Travel::Status::DE::DBRIS::Formation->new( + json => $wagonorder ); }; if ( $wr and $wr->sectors @@ -1709,6 +2087,7 @@ sub startup { uid => $uid, db => $db, with_data => 1, + with_polyline => 1, with_timestamps => 1, with_visibility => 1, postprocess => 1, @@ -1785,8 +2164,8 @@ sub startup { my $wr; eval { $wr - = Travel::Status::DE::DBWagenreihung->new( - from_json => $in_transit->{data}{wagonorder_dep} ); + = Travel::Status::DE::DBRIS::Formation->new( + json => $in_transit->{data}{wagonorder_dep} ); }; if ( $wr and $wr->carriages @@ -1857,8 +2236,10 @@ sub startup { cancellation => $latest_cancellation, backend_id => $latest->{backend_id}, backend_name => $latest->{backend_name}, + is_dbris => $latest->{is_dbris}, is_iris => $latest->{is_iris}, is_hafas => $latest->{is_hafas}, + is_motis => $latest->{is_motis}, journey_id => $latest->{journey_id}, timestamp => $action_time, timestamp_delta => $now->epoch - $action_time->epoch, @@ -1870,6 +2251,7 @@ sub startup { real_departure => epoch_to_dt( $latest->{real_dep_ts} ), dep_ds100 => $latest->{dep_ds100}, dep_eva => $latest->{dep_eva}, + dep_external_id => $latest->{dep_external_id}, dep_name => $latest->{dep_name}, dep_lat => $latest->{dep_lat}, dep_lon => $latest->{dep_lon}, @@ -1878,6 +2260,7 @@ sub startup { real_arrival => epoch_to_dt( $latest->{real_arr_ts} ), arr_ds100 => $latest->{arr_ds100}, arr_eva => $latest->{arr_eva}, + arr_external_id => $latest->{arr_external_id}, arr_name => $latest->{arr_name}, arr_lat => $latest->{arr_lat}, arr_lon => $latest->{arr_lon}, @@ -1918,8 +2301,11 @@ sub startup { ) ? \1 : \0, comment => $status->{comment}, backend => { - id => $status->{backend_id}, - type => $status->{is_hafas} ? 'HAFAS' : 'IRIS-TTS', + id => $status->{backend_id}, + type => $status->{is_dbris} ? 'DBRIS' + : $status->{is_hafas} ? 'HAFAS' + : $status->{is_motis} ? 'MOTIS' + : 'IRIS-TTS', name => $status->{backend_name}, }, fromStation => { @@ -2042,8 +2428,7 @@ sub startup { my $db = $self->pg->db; my $tx = $db->begin; - $self->_checkin_hafas_p( - hafas => 'DB', + $self->_checkin_dbris_p( station => $traewelling->{dep_eva}, train_id => $traewelling->{trip_id}, uid => $uid, @@ -2052,8 +2437,7 @@ sub startup { )->then( sub { $self->log->debug("... handled origin"); - return $self->_checkout_hafas_p( - hafas => 'DB', + return $self->_checkout_journey_p( station => $traewelling->{arr_eva}, train_id => $traewelling->{trip_id}, uid => $uid, @@ -2138,12 +2522,12 @@ sub startup { my @stations = uniq_by { $_->{name} } map { { - name => $_->{to_name}, - latlon => $_->{to_latlon} + name => $_->{to_name} // $_->{arr_name}, + latlon => $_->{to_latlon} // $_->{arr_latlon}, }, { - name => $_->{from_name}, - latlon => $_->{from_latlon} + name => $_->{from_name} // $_->{dep_name}, + latlon => $_->{from_latlon} // $_->{dep_latlon} } } @journeys; @@ -2168,8 +2552,8 @@ sub startup { for my $journey (@polyline_journeys) { my @polyline = @{ $journey->{polyline} }; - my $from_eva = $journey->{from_eva}; - my $to_eva = $journey->{to_eva}; + my $from_eva = $journey->{from_eva} // $journey->{dep_eva}; + my $to_eva = $journey->{to_eva} // $journey->{arr_eva}; my $from_index = first_index { $_->[2] and $_->[2] == $from_eva } @polyline; @@ -2205,7 +2589,7 @@ sub startup { or $to_index == -1 ) { # Fall back to route - delete $journey->{polyline}; + push( @beeline_journeys, $journey ); next; } @@ -2217,7 +2601,6 @@ sub startup { if ( $seen{$key} ) { next; } - $seen{$key} = 1; # direction does not matter at the moment @@ -2227,6 +2610,9 @@ sub startup { . ( $to_index - $from_index ); $seen{$key} = 1; + if ( $from_index > $to_index ) { + ( $to_index, $from_index ) = ( $from_index, $to_index ); + } @polyline = @polyline[ $from_index .. $to_index ]; my @polyline_coords; for my $coord (@polyline) { @@ -2240,13 +2626,19 @@ sub startup { my @route = @{ $journey->{route} }; my $from_index = first_index { - ( $_->[1] and $_->[1] == $journey->{from_eva} ) - or $_->[0] eq $journey->{from_name} + ( $_->[1] + and $_->[1] + == ( $journey->{from_eva} // $journey->{dep_eva} ) ) + or $_->[0] eq + ( $journey->{from_name} // $journey->{dep_name} ) } @route; my $to_index = first_index { - ( $_->[1] and $_->[1] == $journey->{to_eva} ) - or $_->[0] eq $journey->{to_name} + ( $_->[1] + and $_->[1] + == ( $journey->{to_eva} // $journey->{arr_eva} ) ) + or $_->[0] eq + ( $journey->{to_name} // $journey->{arr_name} ) } @route; @@ -2254,15 +2646,15 @@ sub startup { my $rename = $self->app->renamed_station; $from_index = first_index { ( $rename->{ $_->[0] } // $_->[0] ) eq - $journey->{from_name} + ( $journey->{from_name} // $journey->{dep_name} ) } @route; } if ( $to_index == -1 ) { my $rename = $self->app->renamed_station; $to_index = first_index { - ( $rename->{ $_->[0] } // $_->[0] ) eq - $journey->{to_name} + ( $rename->{ $_->[0] } // $_->[0] ) eq + ( $journey->{to_name} // $journey->{arr_name} ) } @route; } @@ -2285,7 +2677,8 @@ sub startup { # and entered manually (-> beeline also shown on map, typically # significantly differs from detailed route) -- unless the user # sets include_manual, of course. - if ( $journey->{edited} & 0x0010 + if ( $journey->{edited} + and $journey->{edited} & 0x0010 and @route <= 2 and not $include_manual ) { diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index f9a2f1e..b328040 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -1,6 +1,7 @@ package Travelynx::Command::database; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Command'; @@ -12,6 +13,7 @@ use JSON; use Travel::Status::DE::EFA; use Travel::Status::DE::HAFAS; use Travel::Status::DE::IRIS::Stations; +use Travel::Status::MOTIS; has description => 'Initialize or upgrade database layout'; @@ -2692,6 +2694,338 @@ qq{select distinct checkout_station_id from in_transit where backend_id = 0;} }, # v58 -> v59 + # DB HAFAS is dead. Default to DB IRIS for now. + sub { + my ($db) = @_; + $db->query( + qq{ + alter table users alter column backend_id set default 0; + update schema_version set version = 59; + } + ); + }, + + # v59 -> v60 + # Add bahn.de / DBRIS backend + sub { + my ($db) = @_; + $db->insert( + 'backends', + { + iris => 0, + hafas => 0, + efa => 0, + ris => 1, + name => 'bahn.de', + }, + ); + $db->query( + qq{ + update schema_version set version = 60; + } + ); + }, + + # v60 -> v61 + # Rename "ris" / "is_ris" to "dbris" / "is_dbris", as it is DB-specific + sub { + my ($db) = @_; + $db->query( + qq{ + drop view in_transit_str; + drop view journeys_str; + drop view users_with_backend; + drop view follows_in_transit; + alter table backends rename column ris to dbris; + create view in_transit_str as select + user_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.name as backend_name, in_transit.backend_id as backend_id, + train_type, train_line, train_no, train_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + polyline_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source + left join backends as backend on in_transit.backend_id = backend.id + ; + create view journeys_str as select + journeys.id as journey_id, user_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.name as backend_name, journeys.backend_id as backend_id, + train_type, train_line, train_no, train_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, edited, route, messages, user_data, + dep_platform, arr_platform + from journeys + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and journeys.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and journeys.backend_id = arr_station.source + left join backends as backend on journeys.backend_id = backend.id + ; + create view users_with_backend as select + users.id as id, users.name as name, status, public_level, + email, password, registered_at, last_seen, + deletion_requested, deletion_notified, use_history, + accept_follows, notifications, profile, backend_id, iris, + hafas, efa, dbris, backend.name as backend_name + from users + left join backends as backend on users.backend_id = backend.id + ; + create view follows_in_transit as select + r1.subject_id as follower_id, user_id as followee_id, + users.name as followee_name, + train_type, train_line, train_no, train_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.name as backend_name, in_transit.backend_id as backend_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + polyline_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join relations as r1 on r1.predicate = 1 and r1.object_id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source + left join backends as backend on in_transit.backend_id = backend.id + order by checkin_time desc + ; + update schema_version set version = 61; + } + ); + }, + + # v61 -> v62 + # Add MOTIS backend type, add RNV and transitous MOTIS backends + sub { + my ($db) = @_; + $db->query( + qq{ + alter table backends add column motis bool default false; + alter table schema_version add column motis varchar(12); + + create table stations_external_ids ( + eva serial not null primary key, + backend_id smallint not null, + external_id text not null, + + unique (backend_id, external_id), + foreign key (eva, backend_id) references stations (eva, source) + ); + + create view stations_with_external_ids as select + stations.*, stations_external_ids.external_id + from stations + left join stations_external_ids on + stations.eva = stations_external_ids.eva and + stations.source = stations_external_ids.backend_id + ; + + drop view in_transit_str; + drop view journeys_str; + drop view users_with_backend; + drop view follows_in_transit; + + create view in_transit_str as select + user_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.motis as is_motis, + backend.name as backend_name, in_transit.backend_id as backend_id, + train_type, train_line, train_no, train_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + dep_station_external_id.external_id as dep_external_id, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + arr_station_external_id.external_id as arr_external_id, + polyline_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source + left join stations_external_ids as dep_station_external_id on checkin_station_id = dep_station_external_id.eva and in_transit.backend_id = dep_station_external_id.backend_id + left join stations_external_ids as arr_station_external_id on checkout_station_id = arr_station_external_id.eva and in_transit.backend_id = arr_station_external_id.backend_id + left join backends as backend on in_transit.backend_id = backend.id + ; + create view journeys_str as select + journeys.id as journey_id, user_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.motis as is_motis, + backend.name as backend_name, journeys.backend_id as backend_id, + train_type, train_line, train_no, train_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + dep_station_external_id.external_id as dep_external_id, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + arr_station_external_id.external_id as arr_external_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, edited, route, messages, user_data, + dep_platform, arr_platform + from journeys + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and journeys.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and journeys.backend_id = arr_station.source + left join stations_external_ids as dep_station_external_id on checkin_station_id = dep_station_external_id.eva and journeys.backend_id = dep_station_external_id.backend_id + left join stations_external_ids as arr_station_external_id on checkout_station_id = arr_station_external_id.eva and journeys.backend_id = arr_station_external_id.backend_id + left join backends as backend on journeys.backend_id = backend.id + ; + create view users_with_backend as select + users.id as id, users.name as name, status, public_level, + email, password, registered_at, last_seen, + deletion_requested, deletion_notified, use_history, + accept_follows, notifications, profile, backend_id, iris, + hafas, efa, dbris, motis, backend.name as backend_name + from users + left join backends as backend on users.backend_id = backend.id + ; + create view follows_in_transit as select + r1.subject_id as follower_id, user_id as followee_id, + users.name as followee_name, + train_type, train_line, train_no, train_id, + backend.iris as is_iris, backend.hafas as is_hafas, + backend.efa as is_efa, backend.dbris as is_dbris, + backend.motis as is_motis, + backend.name as backend_name, in_transit.backend_id as backend_id, + extract(epoch from checkin_time) as checkin_ts, + extract(epoch from sched_departure) as sched_dep_ts, + extract(epoch from real_departure) as real_dep_ts, + checkin_station_id as dep_eva, + dep_station.ds100 as dep_ds100, + dep_station.name as dep_name, + dep_station.lat as dep_lat, + dep_station.lon as dep_lon, + extract(epoch from checkout_time) as checkout_ts, + extract(epoch from sched_arrival) as sched_arr_ts, + extract(epoch from real_arrival) as real_arr_ts, + checkout_station_id as arr_eva, + arr_station.ds100 as arr_ds100, + arr_station.name as arr_name, + arr_station.lat as arr_lat, + arr_station.lon as arr_lon, + polyline_id, + polylines.polyline as polyline, + visibility, + coalesce(visibility, users.public_level & 127) as effective_visibility, + cancelled, route, messages, user_data, + dep_platform, arr_platform, data + from in_transit + left join polylines on polylines.id = polyline_id + left join users on users.id = user_id + left join relations as r1 on r1.predicate = 1 and r1.object_id = user_id + left join stations as dep_station on checkin_station_id = dep_station.eva and in_transit.backend_id = dep_station.source + left join stations as arr_station on checkout_station_id = arr_station.eva and in_transit.backend_id = arr_station.source + left join backends as backend on in_transit.backend_id = backend.id + order by checkin_time desc + ; + } + ); + $db->query( + qq{ + update schema_version set version = 62; + } + ); + }, + + # v62 -> v63 # Add EFA backend support sub { my ($db) = @_; @@ -2893,7 +3227,7 @@ sub sync_stations { } } -sub sync_efa { +sub sync_backends_efa { my ($db) = @_; for my $service ( Travel::Status::DE::EFA::get_services() ) { my $present = $db->select( @@ -2923,7 +3257,7 @@ sub sync_efa { { efa => $Travel::Status::DE::EFA::VERSION } ); } -sub sync_hafas { +sub sync_backends_hafas { my ($db) = @_; for my $service ( Travel::Status::DE::HAFAS::get_services() ) { my $present = $db->select( @@ -2941,7 +3275,7 @@ sub sync_hafas { iris => 0, hafas => 1, efa => 0, - ris => 0, + dbris => 0, name => $service->{shortname}, }, { on_conflict => undef } @@ -2953,6 +3287,37 @@ sub sync_hafas { { hafas => $Travel::Status::DE::HAFAS::VERSION } ); } +sub sync_backends_motis { + my ($db) = @_; + for my $service ( Travel::Status::MOTIS::get_services() ) { + my $present = $db->select( + 'backends', + 'count(*) as count', + { + motis => 1, + name => $service->{shortname} + } + )->hash->{count}; + if ( not $present ) { + $db->insert( + 'backends', + { + iris => 0, + hafas => 0, + efa => 0, + dbris => 0, + motis => 1, + name => $service->{shortname}, + }, + { on_conflict => undef } + ); + } + } + + $db->update( 'schema_version', + { motis => $Travel::Status::MOTIS::VERSION } ); +} + sub setup_db { my ($db) = @_; my $tx = $db->begin; @@ -3048,7 +3413,7 @@ sub migrate_db { else { say "Synchronizing with Travel::Status::DE::EFA $Travel::Status::DE::EFA::VERSION"; - sync_efa($db); + sync_backends_efa($db); } my $hafas_version = get_schema_version( $db, 'hafas' ); @@ -3059,7 +3424,18 @@ sub migrate_db { else { say "Synchronizing with Travel::Status::DE::HAFAS $Travel::Status::DE::HAFAS::VERSION"; - sync_hafas($db); + sync_backends_hafas($db); + } + + my $motis_version = get_schema_version( $db, 'motis' ) // '0'; + say "Found backend table for Motis v${motis_version}"; + if ( $motis_version eq $Travel::Status::MOTIS::VERSION ) { + say 'Backend table is up-to-date'; + } + else { + say +"Synchronizing with Travel::Status::MOTIS $Travel::Status::MOTIS::VERSION"; + sync_backends_motis($db); } $db->update( 'schema_version', diff --git a/lib/Travelynx/Command/traewelling.pm b/lib/Travelynx/Command/traewelling.pm index ed40371..e4e0134 100644 --- a/lib/Travelynx/Command/traewelling.pm +++ b/lib/Travelynx/Command/traewelling.pm @@ -122,12 +122,12 @@ sub push_sync { my ($status) = @_; $push_result{ $status->{http} } += 1; } - )->catch( + )->catch( sub { my ($status) = @_; $push_result{ $status->{http} // 0 } += 1; } - )->wait; + )->wait; } return \%push_result; diff --git a/lib/Travelynx/Command/work.pm b/lib/Travelynx/Command/work.pm index 98f478a..2b01cb2 100644 --- a/lib/Travelynx/Command/work.pm +++ b/lib/Travelynx/Command/work.pm @@ -1,11 +1,14 @@ package Travelynx::Command::work; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Command'; use Mojo::Promise; +use utf8; + use DateTime; use JSON; use List::Util; @@ -33,7 +36,10 @@ sub run { $self->app->log->debug("Removed ${num_incomplete} incomplete checkins"); } - my $errors = 0; + my $errors = 0; + my $backend_issues = 0; + my $rate_limit_counts = 0; + my $dbris_rate_limited = 0; for my $entry ( $self->app->in_transit->get_all_active ) { @@ -47,6 +53,235 @@ sub run { my $arr = $entry->{arr_eva}; my $train_id = $entry->{train_id}; + if ( $entry->{is_dbris} ) { + + eval { + + Mojo::Promise->timer( $dbris_rate_limited ? 4.5 : 1.0 )->then( + sub { + return $self->app->dbris->get_journey_p( + trip_id => $train_id ); + } + )->then( + sub { + my ($journey) = @_; + + $dbris_rate_limited = 0; + + my $found_dep; + my $found_arr; + for my $stop ( $journey->route ) { + if ( $stop->eva == $dep ) { + $found_dep = $stop; + } + if ( $arr and $stop->eva == $arr ) { + $found_arr = $stop; + last; + } + } + if ( not $found_dep ) { + $self->app->log->debug( + "Did not find $dep within journey $train_id"); + return; + } + + if ( $found_dep->rt_dep ) { + $self->app->in_transit->update_departure_dbris( + uid => $uid, + journey => $journey, + stop => $found_dep, + dep_eva => $dep, + arr_eva => $arr, + train_id => $train_id, + ); + } + if ( $found_dep->sched_dep + and $found_dep->dep->epoch > $now->epoch ) + { + $self->app->add_wagonorder( + uid => $uid, + train_id => $train_id, + is_departure => 1, + eva => $dep, + datetime => $found_dep->sched_dep, + train_type => $journey->type, + train_no => $journey->number, + ); + $self->app->add_stationinfo( $uid, 1, + $train_id, $found_dep->eva ); + } + + if ( $found_arr and $found_arr->rt_arr ) { + $self->app->in_transit->update_arrival_dbris( + uid => $uid, + journey => $journey, + train_id => $train_id, + stop => $found_arr, + dep_eva => $dep, + arr_eva => $arr + ); + if ( $found_arr->arr->epoch - $now->epoch < 600 ) { + $self->app->add_wagonorder( + uid => $uid, + train_id => $train_id, + is_arrival => 1, + eva => $arr, + datetime => $found_arr->sched_dep, + train_type => $journey->type, + train_no => $journey->number, + ); + $self->app->add_stationinfo( $uid, 0, + $train_id, $found_dep->eva, + $found_arr->eva ); + } + } + if ( $found_arr and $found_arr->is_cancelled ) { + + # check out (adds a cancelled journey and resets journey state + # to destination selection) + $self->app->checkout_p( + station => $arr, + force => 0, + dep_eva => $dep, + arr_eva => $arr, + uid => $uid + )->wait; + } + } + )->catch( + sub { + my ($err) = @_; + $self->app->log->debug( +"work($uid) @ DBRIS $entry->{backend_name}: journey: $err" + ); + if ( $err =~ m{HTTP 429} ) { + $dbris_rate_limited = 1; + $rate_limit_counts += 1; + } + else { + $backend_issues += 1; + } + } + )->wait; + + if ( $arr + and $entry->{real_arr_ts} + and $now->epoch - $entry->{real_arr_ts} > 600 ) + { + $self->app->checkout_p( + station => $arr, + force => 2, + dep_eva => $dep, + arr_eva => $arr, + uid => $uid + )->wait; + } + }; + if ($@) { + $errors += 1; + $self->app->log->error( + "work($uid) @ DBRIS $entry->{backend_name}: $@"); + } + next; + } + + if ( $entry->{is_motis} ) { + + eval { + $self->app->motis->get_trip_p( + service => $entry->{backend_name}, + trip_id => $train_id, + )->then( + sub { + my ($journey) = @_; + + for my $stopover ( $journey->stopovers ) { + if ( not defined $stopover->stop->{eva} ) { + + # Looks like MOTIS / transitous station IDs can change after the fact. + # So let's be safe rather than sorry, even if this causes way too many calls to the slow path + # (Stations::get_by_external_id uses string lookups). + # This function call implicitly sets $stopover->stop->{eva} for MOTIS backends. + $self->app->stations->add_or_update( + stop => $stopover->stop, + motis => $entry->{backend_name}, + ); + } + } + + my $found_departure; + my $found_arrival; + for my $stopover ( $journey->stopovers ) { + if ( $stopover->stop->{eva} == $dep ) { + $found_departure = $stopover; + } + + if ( $arr and $stopover->stop->{eva} == $arr ) { + $found_arrival = $stopover; + last; + } + } + + if ( not $found_departure ) { + $self->app->log->debug( + "Did not find $dep within trip $train_id"); + return; + } + + if ( $found_departure->realtime_departure ) { + $self->app->in_transit->update_departure_motis( + uid => $uid, + journey => $journey, + stopover => $found_departure, + dep_eva => $dep, + arr_eva => $arr, + train_id => $train_id, + ); + } + + if ( $found_arrival + and $found_arrival->realtime_arrival ) + { + $self->app->in_transit->update_arrival_motis( + uid => $uid, + journey => $journey, + train_id => $train_id, + stopover => $found_arrival, + dep_eva => $dep, + arr_eva => $arr + ); + } + } + )->catch( + sub { + my ($err) = @_; + $self->app->log->error( +"work($uid) @ MOTIS $entry->{backend_name}: journey: $err" + ); + } + )->wait; + + if ( $arr + and $entry->{real_arr_ts} + and $now->epoch - $entry->{real_arr_ts} > 600 ) + { + $self->app->checkout_p( + station => $arr, + force => 2, + dep_eva => $dep, + arr_eva => $arr, + uid => $uid + )->wait; + } + }; + if ($@) { + $errors += 1; + $self->app->log->error( + "work($uid) @ MOTIS $entry->{backend_name}: $@"); + } + next; + } + if ( $entry->{is_hafas} ) { eval { @@ -83,22 +318,27 @@ sub run { dep_eva => $dep, arr_eva => $arr ); - if ( $entry->{backend_id} <= 1 - and $journey->class <= 16 - and $found_dep->rt_dep->epoch > $now->epoch ) - { - $self->app->add_wagonorder( - uid => $uid, - train_id => $journey->id, - is_departure => 1, - eva => $dep, - datetime => $found_dep->sched_dep, - train_type => $journey->type, - train_no => $journey->number, - ); - $self->app->add_stationinfo( $uid, 1, - $journey->id, $found_dep->loc->eva ); - } + } + if ( + $found_dep->sched_dep + and ( $entry->{backend_id} <= 1 + or $entry->{backend_name} eq 'VRN' + or $entry->{backend_name} eq 'ÖBB' ) + and $journey->class <= 16 + and $found_dep->dep->epoch > $now->epoch + ) + { + $self->app->add_wagonorder( + uid => $uid, + train_id => $journey->id, + is_departure => 1, + eva => $dep, + datetime => $found_dep->sched_dep, + train_type => $journey->type =~ s{ +$}{}r, + train_no => $journey->number, + ); + $self->app->add_stationinfo( $uid, 1, + $journey->id, $found_dep->loc->eva ); } if ( $found_arr and $found_arr->rt_arr ) { @@ -109,10 +349,15 @@ sub run { dep_eva => $dep, arr_eva => $arr ); - if ( $entry->{backend_id} <= 1 + if ( + ( + $entry->{backend_id} <= 1 + or $entry->{backend_name} eq 'VRN' + or $entry->{backend_name} eq 'ÖBB' + ) and $journey->class <= 16 - and $found_arr->rt_arr->epoch - $now->epoch - < 600 ) + and $found_arr->arr->epoch - $now->epoch < 600 + ) { $self->app->add_wagonorder( uid => $uid, @@ -132,10 +377,12 @@ sub run { )->catch( sub { my ($err) = @_; + $backend_issues += 1; if ( $err - =~ m{svcResL\[0\][.]err is (?:FAIL|PARAMETER)$} ) + =~ m{svcResL\[0\][.]err is (?:FAIL|PARAMETER)$} + or $err =~ m{timeout} ) { - # HAFAS do be weird. These are not actionable. + # These are not actionable. $self->app->log->debug( "work($uid) @ HAFAS $entry->{backend_name}: journey: $err" ); @@ -340,6 +587,7 @@ sub run { )->catch( sub { my ($error) = @_; + $backend_issues += 1; $self->app->log->error( "work($uid) @ IRIS: arrival: $error"); $errors += 1; @@ -363,13 +611,13 @@ sub run { if ( $self->app->mode eq 'development' ) { $self->app->log->debug( 'POST ' . $self->app->config->{influxdb}->{url} - . " worker runtime_seconds=${worker_duration},errors=${errors}" + . " worker runtime_seconds=${worker_duration},errors=${errors},backend_errors=${backend_issues},ratelimit_count=${rate_limit_counts}" ); } else { $self->app->ua->post_p( $self->app->config->{influxdb}->{url}, - "worker runtime_seconds=${worker_duration},errors=${errors}" ) - ->wait; +"worker runtime_seconds=${worker_duration},errors=${errors},backend_errors=${backend_issues},ratelimit_count=${rate_limit_counts}" + )->wait; } } diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 97a3cf6..74c272f 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -1,6 +1,7 @@ package Travelynx::Controller::Account; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Controller'; @@ -1066,8 +1067,15 @@ sub backend_form { if ( $backend->{iris} ) { $type = 'IRIS-TTS'; $backend->{name} = 'IRIS'; - $backend->{longname} = 'Deutsche Bahn (IRIS-TTS)'; + $backend->{longname} = 'Deutsche Bahn: IRIS-TTS'; $backend->{homepage} = 'https://www.bahn.de'; + $backend->{legacy} = 1; + } + elsif ( $backend->{dbris} ) { + $type = 'DBRIS'; + $backend->{longname} = 'Deutsche Bahn: bahn.de'; + $backend->{homepage} = 'https://www.bahn.de'; + $backend->{recommended} = 1; } elsif ( $backend->{efa} ) { if ( my $s = $self->efa->get_service( $backend->{name} ) ) { @@ -1112,7 +1120,28 @@ sub backend_form { } } elsif ( $backend->{hafas} ) { - if ( my $s = $self->hafas->get_service( $backend->{name} ) ) { + + # These backends lack a journey endpoint or are no longer + # operational and are thus useless for travelynx + if ( $backend->{name} eq 'Resrobot' + or $backend->{name} eq 'TPG' + or $backend->{name} eq 'VRN' + or $backend->{name} eq 'DB' ) + { + $type = undef; + } + + # PKP is behind a GeoIP filter. Only list it if travelynx.conf + # indicates that our IP is allowed or provides a proxy. + elsif ( + $backend->{name} eq 'PKP' + and not( $self->app->config->{hafas}{PKP}{geoip_ok} + or $self->app->config->{hafas}{PKP}{proxy} ) + ) + { + $type = undef; + } + elsif ( my $s = $self->hafas->get_service( $backend->{name} ) ) { $type = 'HAFAS'; $backend->{longname} = $s->{name}; $backend->{homepage} = $s->{homepage}; @@ -1120,6 +1149,13 @@ sub backend_form { @{ $s->{coverage}{regions} // [] } ]; $backend->{has_area} = $s->{coverage}{area} ? 1 : 0; + if ( $backend->{name} eq 'ÖBB' ) { + $backend->{recommended} = 1; + } + else { + $backend->{association} = 1; + } + if ( $s->{coverage}{area} and $s->{coverage}{area}{type} eq 'Polygon' @@ -1153,19 +1189,58 @@ sub backend_form { $type = undef; } } + elsif ( $backend->{motis} ) { + my $s = $self->motis->get_service( $backend->{name} ); + + $type = 'MOTIS'; + $backend->{longname} = $s->{name}; + $backend->{homepage} = $s->{homepage}; + $backend->{regions} = [ map { $place_map{$_} // $_ } + @{ $s->{coverage}{regions} // [] } ]; + $backend->{has_area} = $s->{coverage}{area} ? 1 : 0; + $backend->{experimental} = 1; + + if ( $backend->{name} eq 'transitous' ) { + $backend->{regions} = ['Weltweit']; + } + if ( $backend->{name} eq 'RNV' ) { + $backend->{homepage} = 'https://rnv-online.de/'; + } + + if ( + $s->{coverage}{area} + and $s->{coverage}{area}{type} eq 'Polygon' + and $self->lonlat_in_polygon( + $s->{coverage}{area}{coordinates}, + [ $user_lon, $user_lat ] + ) + ) + { + push( @suggested_backends, $backend ); + } + elsif ( $s->{coverage}{area} + and $s->{coverage}{area}{type} eq 'MultiPolygon' ) + { + for my $s_poly ( @{ $s->{coverage}{area}{coordinates} // [] } ) + { + if ( + $self->lonlat_in_polygon( + $s_poly, [ $user_lon, $user_lat ] + ) + ) + { + push( @suggested_backends, $backend ); + last; + } + } + } + } $backend->{type} = $type; } - # These backends lack a journey endpoint and are useless for travelynx - @backends - = grep { $_->{name} ne 'Resrobot' and $_->{name} ne 'TPG' } @backends; - - my $iris = shift @backends; - - @backends - = sort { $a->{name} cmp $b->{name} } grep { $_->{type} } @backends; - - unshift( @backends, $iris ); + @backends = map { $_->[1] } + sort { $a->[0] cmp $b->[0] } + map { [ lc( $_->{name} ), $_ ] } grep { $_->{type} } @backends; $self->render( 'select_backend', diff --git a/lib/Travelynx/Controller/Api.pm b/lib/Travelynx/Controller/Api.pm index f31195a..572d3fa 100755 --- a/lib/Travelynx/Controller/Api.pm +++ b/lib/Travelynx/Controller/Api.pm @@ -21,6 +21,9 @@ sub sanitize { if ( not defined $value ) { return undef; } + if ( not defined $type ) { + return $value ? ( '' . $value ) : undef; + } if ( $type eq '' ) { return '' . $value; } @@ -184,8 +187,13 @@ sub travel_v1 { my $from_station = sanitize( q{}, $payload->{fromStation} ); my $to_station = sanitize( q{}, $payload->{toStation} ); my $train_id; + my $dbris = sanitize( undef, $payload->{dbris} ); my $hafas = sanitize( undef, $payload->{hafas} ); - $hafas //= exists $payload->{train}{journeyID} ? 'DB' : undef; + my $motis = sanitize( undef, $payload->{motis} ); + + if ( not $hafas and exists $payload->{train}{journeyID} ) { + $dbris //= 'bahn.de'; + } if ( not( @@ -209,6 +217,7 @@ sub travel_v1 { } if ( not $hafas + and not $dbris and not $self->stations->search( $from_station, backend_id => 1 ) ) { $self->render( @@ -225,6 +234,7 @@ sub travel_v1 { if ( $to_station and not $hafas + and not $dbris and not $self->stations->search( $to_station, backend_id => 1 ) ) { $self->render( @@ -288,6 +298,8 @@ sub travel_v1 { train_id => $train_id, uid => $uid, hafas => $hafas, + dbris => $dbris, + motis => $motis, ); } )->then( diff --git a/lib/Travelynx/Controller/Profile.pm b/lib/Travelynx/Controller/Profile.pm index c35642d..db30d36 100755 --- a/lib/Travelynx/Controller/Profile.pm +++ b/lib/Travelynx/Controller/Profile.pm @@ -111,6 +111,13 @@ sub profile { $status->{arr_name} = undef; } + my $map_data = {}; + if ( $status->{checked_in} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } + my @journeys; if ( @@ -190,6 +197,8 @@ sub profile { : 0, journey => $status, journeys => [@journeys], + with_map => 1, + %{$map_data}, } ); } @@ -494,6 +503,13 @@ sub user_status { $og_data{description} = $tw_data{description} = q{}; } + my $map_data = {}; + if ( $status->{checked_in} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } + $self->respond_to( json => { json => { @@ -516,7 +532,9 @@ sub user_status { journey => $status, twitter => \%tw_data, opengraph => \%og_data, - version => $self->app->config->{version} // 'UNKNOWN', + with_map => 1, + %{$map_data}, + version => $self->app->config->{version} // 'UNKNOWN', }, ); } @@ -555,6 +573,7 @@ sub status_card { my $status = $self->get_user_status( $user->{id} ); my $visibility; + my $map_data = {}; if ( $status->{checked_in} or $status->{arr_name} ) { my $visibility = $status->{effective_visibility}; if ( @@ -579,12 +598,19 @@ sub status_card { $status->{arr_name} = undef; } + if ( $status->{checked_in} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } + $self->render( '_public_status_card', name => $name, privacy => $user, journey => $status, from_profile => $self->param('profile') ? 1 : 0, + %{$map_data}, ); } diff --git a/lib/Travelynx/Controller/Traewelling.pm b/lib/Travelynx/Controller/Traewelling.pm index 3cdeff8..6aa789c 100644 --- a/lib/Travelynx/Controller/Traewelling.pm +++ b/lib/Travelynx/Controller/Traewelling.pm @@ -29,7 +29,7 @@ sub oauth { redirect_uri => $self->base_url_for('/oauth/traewelling')->to_abs->scheme( $self->app->mode eq 'development' ? 'http' : 'https' - )->to_string, + )->to_string, scope => 'read-statuses write-statuses' } )->then( diff --git a/lib/Travelynx/Controller/Traveling.pm b/lib/Travelynx/Controller/Traveling.pm index 53d28a9..de1a547 100755 --- a/lib/Travelynx/Controller/Traveling.pm +++ b/lib/Travelynx/Controller/Traveling.pm @@ -1,6 +1,7 @@ package Travelynx::Controller::Traveling; # Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later use Mojo::Base 'Mojolicious::Controller'; @@ -10,6 +11,7 @@ use DateTime::Format::Strptime; use List::Util qw(uniq min max); use List::UtilsBy qw(max_by uniq_by); use List::MoreUtils qw(first_index); +use Mojo::UserAgent; use Mojo::Promise; use Text::CSV; use Travel::Status::DE::IRIS::Stations; @@ -41,6 +43,18 @@ sub get_connecting_trains_p { my $promise = Mojo::Promise->new; + if ( $user->{backend_dbris} ) { + + # We do get a little bit of via information, so this might work in some + # cases. But not reliably. Probably best to leave it out entirely then. + return $promise->reject; + } + if ( $user->{backend_motis} ) { + + # FIXME: The following code can't handle external_ids currently + return $promise->reject; + } + if ( $opt{eva} ) { if ( $use_history & 0x01 ) { $eva = $opt{eva}; @@ -105,6 +119,8 @@ sub get_connecting_trains_p { my $iris_promise = Mojo::Promise->new; my %via_count = map { $_->{name} => 0 } @destinations; + my $backend + = $self->stations->get_backend( backend_id => $opt{backend_id} ); if ( $opt{backend_id} == 0 ) { $self->iris->get_departures_p( station => $eva, @@ -259,9 +275,11 @@ sub get_connecting_trains_p { } )->wait; } - else { - my $hafas_service - = $self->stations->get_hafas_name( backend_id => $opt{backend_id} ); + elsif ( $backend->{dbris} ) { + return $promise->reject; + } + elsif ( $backend->{hafas} ) { + my $hafas_service = $backend->{name}; $self->hafas->get_departures_p( service => $hafas_service, eva => $eva, @@ -341,6 +359,12 @@ sub homepage { $self->stash( timeline => [@timeline] ); my @recent_targets; if ( $status->{checked_in} ) { + my $map_data = {}; + if ( $status->{arr_name} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } my $journey_visibility = $self->compute_effective_visibility( $user->{default_visibility_str}, @@ -359,6 +383,8 @@ sub homepage { journey_visibility => $journey_visibility, connections_iris => $connections_iris, connections_hafas => $connections_hafas, + with_map => 1, + %{$map_data}, ); $self->users->mark_seen( uid => $uid ); } @@ -369,6 +395,8 @@ sub homepage { user => $user, user_status => $status, journey_visibility => $journey_visibility, + with_map => 1, + %{$map_data}, ); $self->users->mark_seen( uid => $uid ); } @@ -381,13 +409,15 @@ sub homepage { user => $user, user_status => $status, journey_visibility => $journey_visibility, + with_map => 1, + %{$map_data}, ); $self->users->mark_seen( uid => $uid ); return; } } else { - @recent_targets = uniq_by { $_->{eva} } + @recent_targets = uniq_by { $_->{external_id_or_eva} } $self->journeys->get_latest_checkout_stations( uid => $uid ); } $self->render( @@ -419,6 +449,12 @@ sub status_card { $self->stash( timeline => [@timeline] ); if ( $status->{checked_in} ) { + my $map_data = {}; + if ( $status->{arr_name} ) { + $map_data = $self->journeys_to_map_data( + journeys => [$status], + ); + } my $journey_visibility = $self->compute_effective_visibility( $self->current_user->{default_visibility_str}, @@ -436,6 +472,7 @@ sub status_card { journey_visibility => $journey_visibility, connections_iris => $connections_iris, connections_hafas => $connections_hafas, + %{$map_data}, ); } )->catch( @@ -444,6 +481,7 @@ sub status_card { '_checked_in', journey => $status, journey_visibility => $journey_visibility, + %{$map_data}, ); } )->wait; @@ -453,6 +491,7 @@ sub status_card { '_checked_in', journey => $status, journey_visibility => $journey_visibility, + %{$map_data}, ); } elsif ( $status->{cancellation} ) { @@ -523,15 +562,74 @@ sub geolocation { return; } - my $hafas_service - = $self->stations->get_hafas_name( backend_id => $backend_id ); + my ( $dbris_service, $hafas_service, $motis_service ); + my $backend = $self->stations->get_backend( backend_id => $backend_id ); + if ( $backend->{dbris} ) { + $dbris_service = $backend->{name}; + } + elsif ( $backend->{hafas} ) { + $hafas_service = $backend->{name}; + } + elsif ( $backend->{motis} ) { + $motis_service = $backend->{name}; + } - if ($hafas_service) { + if ($dbris_service) { $self->render_later; + Travel::Status::DE::DBRIS->new_p( + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + geoSearch => { + latitude => $lat, + longitude => $lon + } + )->then( + sub { + my ($dbris) = @_; + my @results = map { + { + name => $_->name, + eva => $_->eva, + distance => 0, + dbris => $dbris_service, + } + } $dbris->results; + if ( @results > 10 ) { + @results = @results[ 0 .. 9 ]; + } + $self->render( + json => { + candidates => [@results], + } + ); + } + )->catch( + sub { + my ($err) = @_; + $self->render( + json => { + candidates => [], + warning => $err, + } + ); + } + )->wait; + return; + } + elsif ($hafas_service) { + $self->render_later; + + my $agent = $self->ua; + if ( my $proxy = $self->app->config->{hafas}{$hafas_service}{proxy} ) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); + } + Travel::Status::DE::HAFAS->new_p( promise => 'Mojo::Promise', - user_agent => $self->ua, + user_agent => $agent, service => $hafas_service, geoSearch => { lat => $lat, @@ -571,6 +669,54 @@ sub geolocation { return; } + elsif ($motis_service) { + $self->render_later; + + Travel::Status::MOTIS->new_p( + promise => 'Mojo::Promise', + user_agent => $self->ua, + + service => $motis_service, + stops_by_coordinate => { + lat => $lat, + lon => $lon + } + )->then( + sub { + my ($motis) = @_; + my @motis = map { + { + id => $_->id, + name => $_->name, + distance => 0, + motis => $motis_service, + } + } $motis->results; + + if ( @motis > 10 ) { + @motis = @motis[ 0 .. 9 ]; + } + + $self->render( + json => { + candidates => [@motis], + } + ); + } + )->catch( + sub { + my ($err) = @_; + $self->render( + json => { + candidates => [], + warning => $err, + } + ); + } + )->wait; + + return; + } my @iris = map { { @@ -580,6 +726,7 @@ sub geolocation { lon => $_->[0][3], lat => $_->[0][4], distance => $_->[1], + dbris => 0, hafas => 0, } } Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon, @@ -648,10 +795,13 @@ sub travel_action { $promise->then( sub { return $self->checkin_p( - hafas => $params->{hafas}, - station => $params->{station}, - train_id => $params->{train}, - ts => $params->{ts}, + dbris => $params->{dbris}, + hafas => $params->{hafas}, + motis => $params->{motis}, + station => $params->{station}, + train_id => $params->{train}, + train_suffix => $params->{suffix}, + ts => $params->{ts}, ); } )->then( @@ -679,7 +829,10 @@ sub travel_action { my ( $still_checked_in, undef ) = @_; if ( my $destination = $params->{dest} ) { my $station_link = '/s/' . $destination; - if ( $status->{is_hafas} ) { + if ( $status->{is_dbris} ) { + $station_link .= '?dbris=' . $status->{backend_name}; + } + elsif ( $status->{is_hafas} ) { $station_link .= '?hafas=' . $status->{backend_name}; } $self->render( @@ -715,7 +868,10 @@ sub travel_action { sub { my ( $still_checked_in, $error ) = @_; my $station_link = '/s/' . $params->{station}; - if ( $status->{is_hafas} ) { + if ( $status->{is_dbris} ) { + $station_link .= '?dbris=' . $status->{backend_name}; + } + elsif ( $status->{is_hafas} ) { $station_link .= '?hafas=' . $status->{backend_name}; } @@ -766,13 +922,27 @@ sub travel_action { else { my $redir = '/'; if ( $status->{checked_in} or $status->{cancelled} ) { - if ( $status->{is_hafas} ) { + if ( $status->{is_dbris} ) { + $redir + = '/s/' + . $status->{dep_eva} + . '?dbris=' + . $status->{backend_name}; + } + elsif ( $status->{is_hafas} ) { $redir = '/s/' . $status->{dep_eva} . '?hafas=' . $status->{backend_name}; } + elsif ( $status->{is_motis} ) { + $redir + = '/s/' + . $status->{dep_external_id} + . '?motis=' + . $status->{backend_name}; + } else { $redir = '/s/' . $status->{dep_ds100}; } @@ -788,7 +958,9 @@ sub travel_action { elsif ( $params->{action} eq 'cancelled_from' ) { $self->render_later; $self->checkin_p( + dbris => $params->{dbris}, hafas => $params->{hafas}, + motis => $params->{motis}, station => $params->{station}, train_id => $params->{train}, ts => $params->{ts}, @@ -917,12 +1089,38 @@ sub station { $timestamp = DateTime->now( time_zone => 'Europe/Berlin' ); } + my $dbris_service = $self->param('dbris') + // ( $user->{backend_dbris} ? $user->{backend_name} : undef ); my $efa_service = $self->param('efa') // ( $user->{backend_efa} ? $user->{backend_name} : undef ); my $hafas_service = $self->param('hafas') // ( $user->{backend_hafas} ? $user->{backend_name} : undef ); + my $motis_service = $self->param('motis') + // ( $user->{backend_motis} ? $user->{backend_name} : undef ); my $promise; - if ($hafas_service) { + if ($dbris_service) { + if ( $station !~ m{ [@] L = \d+ }x ) { + $self->render_later; + $self->dbris->get_station_id_p($station)->then( + sub { + my ($dbris_station) = @_; + $self->redirect_to( '/s/' . $dbris_station->{id} ); + } + )->catch( + sub { + my ($err) = @_; + $self->redirect_to('/'); + } + )->wait; + return; + } + $promise = $self->dbris->get_departures_p( + station => $station, + timestamp => $timestamp, + lookbehind => 30, + ); + } + elsif ($hafas_service) { $promise = $self->hafas->get_departures_p( service => $hafas_service, eva => $station, @@ -936,7 +1134,36 @@ sub station { service => $efa_service, name => $station, timestamp => $timestamp, - lookbehind => 10, + lookbehind => 30, + lookahead => 30, + ); + } + elsif ($motis_service) { + if ( $station !~ m/.*_.*/ ) { + $self->render_later; + $self->motis->get_station_by_query_p( + service => $motis_service, + query => $station, + )->then( + sub { + my ($motis_station) = @_; + $self->redirect_to( '/s/' . $motis_station->{id} ); + } + )->catch( + sub { + my ($err) = @_; + say "$err"; + + $self->redirect_to('/'); + } + )->wait; + return; + } + $promise = $self->motis->get_departures_p( + service => $motis_service, + station_id => $station, + timestamp => $timestamp, + lookbehind => 30, lookahead => 30, ); } @@ -957,7 +1184,22 @@ sub station { my $now_within_range = abs( $timestamp->epoch - $now ) < 1800 ? 1 : 0; - if ($hafas_service) { + if ($dbris_service) { + + @results = map { $_->[0] } + sort { $b->[1] <=> $a->[1] } + map { [ $_, $_->dep->epoch ] } $status->results; + + $status = { + station_eva => $station, + related_stations => [], + }; + + if ( $station =~ m{ [@] O = (?<name> [^@]+ ) [@] }x ) { + $status->{station_name} = $+{name}; + } + } + elsif ($hafas_service) { @results = map { $_->[0] } sort { $b->[1] <=> $a->[1] } @@ -988,6 +1230,19 @@ sub station { related_stations => [], }; } + elsif ($motis_service) { + @results = map { $_->[0] } + sort { $b->[1] <=> $a->[1] } + map { [ $_, $_->stopover->departure->epoch ] } + $status->results; + + $status = { + station_eva => $station, + station_name => + $status->{results}->[0]->stopover->stop->name, + related_stations => [], + }; + } else { # You can't check into a train which terminates here @@ -1054,8 +1309,10 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, efa => $efa_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1074,8 +1331,10 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, efa => $efa_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1093,8 +1352,10 @@ sub station { $self->render( 'departures', user => $user, + dbris => $dbris_service, efa => $efa_service, hafas => $hafas_service, + motis => $motis_service, eva => $status->{station_eva}, datetime => $timestamp, now_in_range => $now_within_range, @@ -1157,18 +1418,23 @@ sub station { } )->wait; } - elsif ( $err =~ m{svcRes|connection close} ) { + elsif ( $err + =~ m{svcRes|connection close|Service Temporarily Unavailable|Forbidden} + ) + { $self->render( 'bad_gateway', - message => $err, - status => 502 + message => $err, + status => 502, + select_new_backend => 1, ); } elsif ( $err =~ m{timeout}i ) { $self->render( 'gateway_timeout', - message => $err, - status => 504 + message => $err, + status => 504, + select_new_backend => 1, ); } else { @@ -1187,7 +1453,40 @@ sub redirect_to_station { my ($self) = @_; my $station = $self->param('station'); - $self->redirect_to("/s/${station}"); + if ( $self->param('backend_dbris') ) { + $self->render_later; + $self->dbris->get_station_id_p($station)->then( + sub { + my ($dbris_station) = @_; + $self->redirect_to( '/s/' . $dbris_station->{id} ); + } + )->catch( + sub { + my ($err) = @_; + $self->redirect_to('/'); + } + )->wait; + } + elsif ( $self->param('backend_motis') ) { + $self->render_later; + $self->motis->get_station_by_query( + service => $self->param('backend_motis'), + query => $station, + )->then( + sub { + my ($motis_station) = @_; + $self->redirect_to( '/s/' . $motis_station->{id} ); + } + )->catch( + sub { + my ($err) = @_; + $self->redirect_to('/'); + } + )->wait; + } + else { + $self->redirect_to("/s/${station}"); + } } sub cancelled { diff --git a/lib/Travelynx/Helper/DBDB.pm b/lib/Travelynx/Helper/DBDB.pm index 79e9c0b..a310aa3 100644 --- a/lib/Travelynx/Helper/DBDB.pm +++ b/lib/Travelynx/Helper/DBDB.pm @@ -61,7 +61,8 @@ sub has_wagonorder_p { } } - $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} ) + $self->{user_agent}->request_timeout(5) + ->get_p( $url => $self->{header} ) ->then( sub { my ($tx) = @_; @@ -81,7 +82,7 @@ sub has_wagonorder_p { } return; } - )->catch( + )->catch( sub { my ($err) = @_; $self->{log}->debug("${debug_prefix}: n ($err)"); @@ -89,7 +90,7 @@ sub has_wagonorder_p { $promise->reject; return; } - )->wait; + )->wait; return $promise; } @@ -120,7 +121,8 @@ sub get_wagonorder_p { return $promise; } - $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} ) + $self->{user_agent}->request_timeout(5) + ->get_p( $url => $self->{header} ) ->then( sub { my ($tx) = @_; @@ -139,14 +141,14 @@ sub get_wagonorder_p { } return; } - )->catch( + )->catch( sub { my ($err) = @_; $self->{log}->debug("${debug_prefix}: error ${err}"); $promise->reject($err); return; } - )->wait; + )->wait; return $promise; } @@ -163,7 +165,8 @@ sub get_stationinfo_p { return $promise->resolve($content); } - $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} ) + $self->{user_agent}->request_timeout(5) + ->get_p( $url => $self->{header} ) ->then( sub { my ($tx) = @_; @@ -183,7 +186,7 @@ sub get_stationinfo_p { $promise->resolve($json); return; } - )->catch( + )->catch( sub { my ($err) = @_; $self->{log}->debug("get_stationinfo_p(${eva}): Error ${err}"); @@ -191,7 +194,7 @@ sub get_stationinfo_p { $promise->reject($err); return; } - )->wait; + )->wait; return $promise; } diff --git a/lib/Travelynx/Helper/DBRIS.pm b/lib/Travelynx/Helper/DBRIS.pm new file mode 100644 index 0000000..0a46758 --- /dev/null +++ b/lib/Travelynx/Helper/DBRIS.pm @@ -0,0 +1,147 @@ +package Travelynx::Helper::DBRIS; + +# Copyright (C) 2025 Birte Kristina Friesel +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +use strict; +use warnings; +use 5.020; +use utf8; + +use DateTime; +use Encode qw(decode); +use JSON; +use Mojo::Promise; +use Mojo::UserAgent; +use Travel::Status::DE::DBRIS; + +sub new { + my ( $class, %opt ) = @_; + + my $version = $opt{version}; + + $opt{header} + = { 'User-Agent' => +"travelynx/${version} on $opt{root_url} +https://finalrewind.org/projects/travelynx" + }; + + return bless( \%opt, $class ); +} + +sub get_station_id_p { + my ( $self, $station_name ) = @_; + my $promise = Mojo::Promise->new; + Travel::Status::DE::DBRIS->new_p( + locationSearch => $station_name, + cache => $self->{cache}, + lwp_options => { + timeout => 10, + agent => $self->{header}{'User-Agent'}, + }, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + )->then( + sub { + my ($dbris) = @_; + my $found; + for my $result ( $dbris->results ) { + if ( defined $result->eva ) { + $promise->resolve($result); + return; + } + } + $promise->reject("Unable to find station '$station_name'"); + return; + } + )->catch( + sub { + my ($err) = @_; + $promise->reject("'$err' while trying to look up '$station_name'"); + return; + } + )->wait; + return $promise; +} + +sub get_departures_p { + my ( $self, %opt ) = @_; + + my $agent = $self->{user_agent}; + + if ( $opt{station} =~ m{ [@] L = (?<eva> \d+ ) [@] }x ) { + $opt{station} = { + eva => $+{eva}, + id => $opt{station}, + }; + } + + my $when = ( + $opt{timestamp} + ? $opt{timestamp}->clone + : DateTime->now( time_zone => 'Europe/Berlin' ) + )->subtract( minutes => $opt{lookbehind} ); + return Travel::Status::DE::DBRIS->new_p( + station => $opt{station}, + datetime => $when, + cache => $self->{cache}, + promise => 'Mojo::Promise', + user_agent => $agent->request_timeout(10), + ); +} + +sub get_journey_p { + my ( $self, %opt ) = @_; + + my $promise = Mojo::Promise->new; + my $now = DateTime->now( time_zone => 'Europe/Berlin' ); + + my $agent = $self->{user_agent}; + my $proxy; + if ( my @proxies = @{ $self->{service_config}{'bahn.de'}{proxies} // [] } ) + { + $proxy = $proxies[ int( rand( scalar @proxies ) ) ]; + } + elsif ( my $p = $self->{service_config}{'bahn.de'}{proxy} ) { + $proxy = $p; + } + + if ($proxy) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); + } + + Travel::Status::DE::DBRIS->new_p( + journey => $opt{trip_id}, + with_polyline => $opt{with_polyline}, + cache => $self->{realtime_cache}, + promise => 'Mojo::Promise', + user_agent => $agent->request_timeout(10), + )->then( + sub { + my ($dbris) = @_; + my $journey = $dbris->result; + + if ($journey) { + $self->{log}->debug("get_journey_p($opt{trip_id}): success"); + $promise->resolve($journey); + return; + } + $self->{log}->debug("get_journey_p($opt{trip_id}): no journey"); + $promise->reject('no journey'); + return; + } + )->catch( + sub { + my ($err) = @_; + $self->{log}->debug("get_journey_p($opt{trip_id}): error $err"); + $promise->reject($err); + return; + } + )->wait; + + return $promise; +} + +1; diff --git a/lib/Travelynx/Helper/HAFAS.pm b/lib/Travelynx/Helper/HAFAS.pm index 5b1ee81..c35dfdb 100644 --- a/lib/Travelynx/Helper/HAFAS.pm +++ b/lib/Travelynx/Helper/HAFAS.pm @@ -7,11 +7,13 @@ package Travelynx::Helper::HAFAS; use strict; use warnings; use 5.020; +use utf8; use DateTime; use Encode qw(decode); use JSON; use Mojo::Promise; +use Mojo::UserAgent; use Travel::Status::DE::HAFAS; sub _epoch { @@ -53,58 +55,18 @@ sub get_service { return Travel::Status::DE::HAFAS::get_service($service); } -sub get_json_p { - my ( $self, $url, %opt ) = @_; - - my $cache = $self->{main_cache}; - my $promise = Mojo::Promise->new; +sub get_departures_p { + my ( $self, %opt ) = @_; - if ( $opt{realtime} ) { - $cache = $self->{realtime_cache}; - } - $opt{encoding} //= 'ISO-8859-15'; + $opt{service} //= 'ÖBB'; - if ( my $content = $cache->thaw($url) ) { - return $promise->resolve($content); + my $agent = $self->{user_agent}; + if ( my $proxy = $self->{service_config}{ $opt{service} }{proxy} ) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); } - $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} ) - ->then( - sub { - my ($tx) = @_; - - if ( my $err = $tx->error ) { - $promise->reject( -"hafas->get_json_p($url) returned HTTP $err->{code} $err->{message}" - ); - return; - } - - my $body = decode( $opt{encoding}, $tx->res->body ); - - $body =~ s{^TSLs[.]sls = }{}; - $body =~ s{;$}{}; - $body =~ s{(}{(}g; - $body =~ s{)}{)}g; - my $json = JSON->new->decode($body); - $cache->freeze( $url, $json ); - $promise->resolve($json); - return; - } - )->catch( - sub { - my ($err) = @_; - $self->{log}->info("hafas->get_json_p($url): $err"); - $promise->reject("hafas->get_json_p($url): $err"); - return; - } - )->wait; - return $promise; -} - -sub get_departures_p { - my ( $self, %opt ) = @_; - my $when = ( $opt{timestamp} ? $opt{timestamp}->clone @@ -118,19 +80,28 @@ sub get_departures_p { results => 300, cache => $self->{realtime_cache}, promise => 'Mojo::Promise', - user_agent => $self->{user_agent}->request_timeout(5), + user_agent => $agent->request_timeout(5), ); } sub search_location_p { my ( $self, %opt ) = @_; + $opt{service} //= 'ÖBB'; + + my $agent = $self->{user_agent}; + if ( my $proxy = $self->{service_config}{ $opt{service} }{proxy} ) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); + } + return Travel::Status::DE::HAFAS->new_p( service => $opt{service}, locationSearch => $opt{query}, cache => $self->{realtime_cache}, promise => 'Mojo::Promise', - user_agent => $self->{user_agent}->request_timeout(5), + user_agent => $agent->request_timeout(5), ); } @@ -143,13 +114,22 @@ sub get_tripid_p { my $train_desc = $train->type . ' ' . $train->train_no; $train_desc =~ s{^- }{}; + $opt{service} //= 'ÖBB'; + + my $agent = $self->{user_agent}; + if ( my $proxy = $self->{service_config}{ $opt{service} }{proxy} ) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); + } + Travel::Status::DE::HAFAS->new_p( service => $opt{service}, journeyMatch => $train_desc, datetime => $train->start, cache => $self->{realtime_cache}, promise => 'Mojo::Promise', - user_agent => $self->{user_agent}->request_timeout(10), + user_agent => $agent->request_timeout(10), )->then( sub { my ($hafas) = @_; @@ -195,6 +175,15 @@ sub get_journey_p { my $promise = Mojo::Promise->new; my $now = DateTime->now( time_zone => 'Europe/Berlin' ); + $opt{service} //= 'ÖBB'; + + my $agent = $self->{user_agent}; + if ( my $proxy = $self->{service_config}{ $opt{service} }{proxy} ) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); + } + Travel::Status::DE::HAFAS->new_p( service => $opt{service}, journey => { @@ -203,7 +192,7 @@ sub get_journey_p { with_polyline => $opt{with_polyline}, cache => $self->{realtime_cache}, promise => 'Mojo::Promise', - user_agent => $self->{user_agent}->request_timeout(10), + user_agent => $agent->request_timeout(10), )->then( sub { my ($hafas) = @_; @@ -236,6 +225,15 @@ sub get_route_p { my $promise = Mojo::Promise->new; my $now = DateTime->now( time_zone => 'Europe/Berlin' ); + $opt{service} //= 'ÖBB'; + + my $agent = $self->{user_agent}; + if ( my $proxy = $self->{service_config}{ $opt{service} }{proxy} ) { + $agent = Mojo::UserAgent->new; + $agent->proxy->http($proxy); + $agent->proxy->https($proxy); + } + Travel::Status::DE::HAFAS->new_p( service => $opt{service}, journey => { @@ -246,7 +244,7 @@ sub get_route_p { with_polyline => $opt{with_polyline}, cache => $self->{realtime_cache}, promise => 'Mojo::Promise', - user_agent => $self->{user_agent}->request_timeout(10), + user_agent => $agent->request_timeout(10), )->then( sub { my ($hafas) = @_; diff --git a/lib/Travelynx/Helper/MOTIS.pm b/lib/Travelynx/Helper/MOTIS.pm new file mode 100644 index 0000000..d4e1777 --- /dev/null +++ b/lib/Travelynx/Helper/MOTIS.pm @@ -0,0 +1,158 @@ +package Travelynx::Helper::MOTIS; + +# Copyright (C) 2025 networkException <git@nwex.de> +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +use strict; +use warnings; +use 5.020; +use utf8; + +use DateTime; +use Encode qw(decode); +use JSON; +use Mojo::Promise; +use Mojo::UserAgent; + +use Travel::Status::MOTIS; + +sub _epoch { + my ($dt) = @_; + + return $dt ? $dt->epoch : 0; +} + +sub new { + my ( $class, %opt ) = @_; + + my $version = $opt{version}; + + $opt{header} + = { 'User-Agent' => +"travelynx/${version} on $opt{root_url} +https://finalrewind.org/projects/travelynx" + }; + + return bless( \%opt, $class ); +} + +sub get_service { + my ( $self, $service ) = @_; + + return Travel::Status::MOTIS::get_service($service); +} + +sub get_station_by_query_p { + my ( $self, %opt ) = @_; + + $opt{service} //= 'transitous'; + + my $promise = Mojo::Promise->new; + + Travel::Status::MOTIS->new_p( + cache => $self->{cache}, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + lwp_options => { + timeout => 10, + agent => $self->{header}{'User-Agent'}, + }, + + service => $opt{service}, + stops_by_query => $opt{query}, + )->then( + sub { + my ($motis) = @_; + my $found; + + for my $result ( $motis->results ) { + if ( defined $result->id ) { + $promise->resolve($result); + return; + } + } + + $promise->reject("Unable to find station '$opt{query}'"); + return; + } + )->catch( + sub { + my ($err) = @_; + $promise->reject("'$err' while trying to look up '$opt{query}'"); + return; + } + )->wait; + + return $promise; +} + +sub get_departures_p { + my ( $self, %opt ) = @_; + + $opt{service} //= 'transitous'; + + my $timestamp = ( + $opt{timestamp} + ? $opt{timestamp}->clone + : DateTime->now + )->subtract( minutes => $opt{lookbehind} ); + + return Travel::Status::MOTIS->new_p( + cache => $self->{cache}, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + lwp_options => { + timeout => 10, + agent => $self->{header}{'User-Agent'}, + }, + + service => $opt{service}, + timestamp => $timestamp, + stop_id => $opt{station_id}, + results => 60, + ); +} + +sub get_trip_p { + my ( $self, %opt ) = @_; + + $opt{service} //= 'transitous'; + + my $promise = Mojo::Promise->new; + + Travel::Status::MOTIS->new_p( + with_polyline => $opt{with_polyline}, + cache => $self->{realtime_cache}, + promise => 'Mojo::Promise', + user_agent => Mojo::UserAgent->new, + + service => $opt{service}, + trip_id => $opt{trip_id}, + )->then( + sub { + my ($motis) = @_; + my $journey = $motis->result; + + if ($journey) { + $self->{log}->debug("get_trip_p($opt{trip_id}): success"); + $promise->resolve($journey); + return; + } + + $self->{log}->debug("get_trip_p($opt{trip_id}): no journey"); + $promise->reject('no journey'); + return; + } + )->catch( + sub { + my ($err) = @_; + $self->{log}->debug("get_trip_p($opt{trip_id}): error $err"); + $promise->reject($err); + return; + } + )->wait; + + return $promise; +} + +1; diff --git a/lib/Travelynx/Helper/Traewelling.pm b/lib/Travelynx/Helper/Traewelling.pm index 100a799..66f2a29 100644 --- a/lib/Travelynx/Helper/Traewelling.pm +++ b/lib/Travelynx/Helper/Traewelling.pm @@ -78,7 +78,8 @@ sub get_status_p { $self->{user_agent}->request_timeout(20) ->get_p( "https://traewelling.de/api/v1/user/${username}/statuses?limit=1" => - $header )->then( + $header ) + ->then( sub { my ($tx) = @_; if ( my $err = $tx->error ) { @@ -150,13 +151,13 @@ sub get_status_p { } } } - )->catch( + )->catch( sub { my ($err) = @_; $promise->reject( { text => "v1/${username}/statuses: $err" } ); return; } - )->wait; + )->wait; return $promise; } @@ -238,13 +239,13 @@ sub logout_p { return; } } - )->catch( + )->catch( sub { my ($err) = @_; $promise->reject("v1/auth/logout: $err"); return; } - )->wait; + )->wait; return $promise; } @@ -322,7 +323,8 @@ sub checkin_p { $self->{user_agent}->request_timeout(20) ->post_p( "https://traewelling.de/api/v1/trains/checkin" => $header => json => - $request )->then( + $request ) + ->then( sub { my ($tx) = @_; if ( my $err = $tx->error ) { @@ -368,7 +370,7 @@ sub checkin_p { # on the user status page return; } - )->catch( + )->catch( sub { my ($err) = @_; $self->{log}->debug("... $debug_prefix error: $err"); @@ -381,7 +383,7 @@ sub checkin_p { $promise->reject( { connection => $err } ); return; } - )->wait; + )->wait; return $promise; } diff --git a/lib/Travelynx/Model/InTransit.pm b/lib/Travelynx/Model/InTransit.pm index 62e60f1..cc943b3 100644 --- a/lib/Travelynx/Model/InTransit.pm +++ b/lib/Travelynx/Model/InTransit.pm @@ -1,6 +1,7 @@ package Travelynx::Model::InTransit; -# Copyright (C) 2020-2023 Birte Kristina Friesel +# Copyright (C) 2020-2025 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -12,11 +13,12 @@ use DateTime; use JSON; my %visibility_itoa = ( - 100 => 'public', - 80 => 'travelynx', - 60 => 'followers', - 30 => 'unlisted', - 10 => 'private', + 100 => 'public', + 80 => 'travelynx', + 60 => 'followers', + 30 => 'unlisted', + 10 => 'private', + default => 'default', ); my %visibility_atoi = ( @@ -30,7 +32,7 @@ my %visibility_atoi = ( sub _epoch { my ($dt) = @_; - return $dt ? $dt->epoch : 0; + return $dt ? $dt->epoch : undef; } sub epoch_to_dt { @@ -95,15 +97,21 @@ sub add { my $db = $opt{db} // $self->{pg}->db; my $backend_id = $opt{backend_id}; my $train = $opt{train}; + my $train_suffix = $opt{train_suffix}; my $journey = $opt{journey}; my $stop = $opt{stop}; + my $stopover = $opt{stopover}; my $checkin_station_id = $opt{departure_eva}; my $route = $opt{route}; my $data = $opt{data}; + my $persistent_data; my $json = JSON->new; + my $now = DateTime->now( time_zone => 'Europe/Berlin' ); if ($train) { + + # IRIS $db->insert( 'in_transit', { @@ -111,16 +119,16 @@ sub add { cancelled => $train->departure_is_cancelled ? 1 : 0, checkin_station_id => $checkin_station_id, - checkin_time => DateTime->now( time_zone => 'Europe/Berlin' ), - dep_platform => $train->platform, - train_type => $train->type, - train_line => $train->line_no, - train_no => $train->train_no, - train_id => $train->train_id, - sched_departure => $train->sched_departure, - real_departure => $train->departure, - route => $json->encode($route), - messages => $json->encode( + checkin_time => $now, + dep_platform => $train->platform, + train_type => $train->type, + train_line => $train->line_no, + train_no => $train->train_no, + train_id => $train->train_id, + sched_departure => $train->sched_departure, + real_departure => $train->departure, + route => $json->encode($route), + messages => $json->encode( [ map { [ $_->[0]->epoch, $_->[1] ] } $train->messages ] ), data => JSON->new->encode( @@ -134,7 +142,9 @@ sub add { } ); } - elsif ( $journey and $stop ) { + elsif ( $journey and $stop and $journey->can('product') ) { + + # HAFAS my @route; my $product = $journey->product_at( $stop->loc->eva ) // $journey->product; @@ -169,18 +179,159 @@ sub add { ? 1 : 0, checkin_station_id => $stop->loc->eva, + checkin_time => $now, + dep_platform => $stop->{platform}, + train_type => $product->type // q{}, + train_line => $product->line_no, + train_no => $product->number // q{}, + train_id => $journey->id, + sched_departure => $stop->{sched_dep}, + real_departure => $stop->{rt_dep} // $stop->{sched_dep}, + route => $json->encode( \@route ), + data => JSON->new->encode( + { + rt => $stop->{rt_dep} ? 1 : 0, + %{ $data // {} } + } + ), + backend_id => $backend_id, + } + ); + } + elsif ( $journey and $stop ) { + + # DBRIS + my $number = $journey->train_no // $journey->number // $train_suffix; + + my $line; + if ( defined $journey->line_no and $journey->line_no ne $number ) { + $line = $journey->line_no; + } + elsif ( defined $train_suffix and $train_suffix ne $number ) { + $line = $train_suffix; + } + + my @route; + for my $j_stop ( $journey->route ) { + push( + @route, + [ + $j_stop->name, + $j_stop->eva, + { + sched_arr => _epoch( $j_stop->sched_arr ), + sched_dep => _epoch( $j_stop->sched_dep ), + rt_arr => _epoch( $j_stop->rt_arr ), + rt_dep => _epoch( $j_stop->rt_dep ), + isCancelled => $j_stop->is_cancelled, + arr_delay => $j_stop->arr_delay, + dep_delay => $j_stop->dep_delay, + load => { + FIRST => $j_stop->occupancy_first, + SECOND => $j_stop->occupancy_second + }, + lat => $j_stop->lat, + lon => $j_stop->lon, + } + ] + ); + } + my @messages; + for my $msg ( $journey->messages ) { + if ( not $msg->{ueberschrift} ) { + push( + @{ $data->{him_msg} }, + { + header => q{}, + prio => $msg->{prioritaet}, + lead => $msg->{text} + } + ); + push( + @{ $persistent_data->{him_msg} }, + { + prio => $msg->{prioritaet}, + lead => $msg->{text} + } + ); + } + } + $db->insert( + 'in_transit', + { + user_id => $uid, + cancelled => $stop->is_cancelled + ? 1 + : 0, + checkin_station_id => $stop->eva, + checkin_time => $now, + dep_platform => $stop->platform, + train_type => $journey->type // q{}, + train_line => $line, + train_no => $number, + train_id => $data->{trip_id}, + sched_departure => $stop->sched_dep, + real_departure => $stop->rt_dep // $stop->sched_dep, + route => $json->encode( \@route ), + data => JSON->new->encode( + { + rt => $stop->{rt_dep} ? 1 : 0, + %{ $data // {} } + } + ), + user_data => JSON->new->encode($persistent_data), + backend_id => $backend_id, + } + ); + } + elsif ( $journey and $stopover ) { + + # MOTIS + my @route; + for my $journey_stopover ( $journey->stopovers ) { + push( + @route, + [ + $journey_stopover->stop->name, + $journey_stopover->stop->{eva} + // die('eva not set for stopover'), + { + sched_arr => + _epoch( $journey_stopover->scheduled_arrival ), + sched_dep => + _epoch( $journey_stopover->scheduled_departure ), + rt_arr => _epoch( $journey_stopover->realtime_arrival ), + rt_dep => + _epoch( $journey_stopover->realtime_departure ), + arr_delay => $journey_stopover->arrival_delay, + dep_delay => $journey_stopover->departure_delay, + lat => $journey_stopover->stop->lat, + lon => $journey_stopover->stop->lon, + } + ] + ); + } + + $db->insert( + 'in_transit', + { + user_id => $uid, + cancelled => $stopover->{is_cancelled} + ? 1 + : 0, + checkin_station_id => $stopover->stop->{eva}, checkin_time => DateTime->now( time_zone => 'Europe/Berlin' ), - dep_platform => $stop->{platform}, - train_type => $product->type // q{}, - train_line => $product->line_no, - train_no => $product->number // q{}, + dep_platform => $stopover->track, + train_type => $journey->mode, + train_no => q{}, train_id => $journey->id, - sched_departure => $stop->{sched_dep}, - real_departure => $stop->{rt_dep} // $stop->{sched_dep}, + train_line => $journey->route_name, + sched_departure => $stopover->scheduled_departure, + real_departure => $stopover->departure, route => $json->encode( \@route ), data => JSON->new->encode( { - rt => $stop->{rt_dep} ? 1 : 0, + rt => $stopover->{is_realtime} ? 1 : 0, %{ $data // {} } } ), @@ -264,18 +415,17 @@ sub postprocess { $ret->{route_after} = \@route_after; $ret->{extra_data} = $ret->{data}; $ret->{comment} = $ret->{user_data}{comment}; + $ret->{wagongroups} = $ret->{user_data}{wagongroups}; $ret->{platform_type} = 'Gleis'; - if ( $ret->{train_type} =~ m{ ast | bus | ruf }ix ) { + if ( $ret->{train_type} and $ret->{train_type} =~ m{ ast | bus | ruf }ix ) { $ret->{platform_type} = 'Steig'; } $ret->{visibility_str} - = $ret->{visibility} - ? $visibility_itoa{ $ret->{visibility} } - : 'default'; + = $visibility_itoa{ $ret->{visibility} // 'default' }; $ret->{effective_visibility_str} - = $visibility_itoa{ $ret->{effective_visibility} }; + = $visibility_itoa{ $ret->{effective_visibility} // 'default' }; my @parsed_messages; for my $message ( @{ $ret->{messages} // [] } ) { @@ -299,7 +449,7 @@ sub postprocess { = $dep_info->{rt_arr}->epoch - $epoch; } - for my $station (@route_after) { + for my $station (@route) { if ( @{$station} > 1 ) { # Note: $station->[2]{sched_arr} may already have been @@ -367,7 +517,7 @@ sub get { my $table = 'in_transit'; - if ( $opt{with_timestamps} ) { + if ( $opt{with_timestamps} or $opt{with_polyline} ) { $table = 'in_transit_str'; } @@ -381,13 +531,16 @@ sub get { $ret = $res->hash; } + if ( $opt{with_polyline} and $ret ) { + $ret->{dep_latlon} = [ $ret->{dep_lat}, $ret->{dep_lon} ]; + $ret->{arr_latlon} = [ $ret->{arr_lat}, $ret->{arr_lon} ]; + } + if ( $opt{with_visibility} and $ret ) { $ret->{visibility_str} - = $ret->{visibility} - ? $visibility_itoa{ $ret->{visibility} } - : 'default'; + = $visibility_itoa{ $ret->{visibility} // 'default' }; $ret->{effective_visibility_str} - = $visibility_itoa{ $ret->{effective_visibility} }; + = $visibility_itoa{ $ret->{effective_visibility} // 'default' }; } if ( $opt{postprocess} and $ret ) { @@ -731,6 +884,93 @@ sub update_departure_cancelled { return $rows; } +sub update_departure_dbris { + my ( $self, %opt ) = @_; + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + my $dep_eva = $opt{dep_eva}; + my $arr_eva = $opt{arr_eva}; + my $journey = $opt{journey}; + my $stop = $opt{stop}; + my $json = JSON->new; + + my $res_h = $db->select( 'in_transit', [ 'data', 'user_data' ], + { user_id => $uid } )->expand->hash; + my $ephemeral_data = $res_h ? $res_h->{data} : {}; + my $persistent_data = $res_h ? $res_h->{user_data} : {}; + + if ( $stop->{rt_dep} ) { + $ephemeral_data->{rt} = 1; + } + + $ephemeral_data->{him_msg} = []; + $persistent_data->{him_msg} = []; + for my $msg ( $journey->messages ) { + if ( not $msg->{ueberschrift} ) { + push( + @{ $ephemeral_data->{him_msg} }, + { + header => q{}, + prio => $msg->{prioritaet}, + lead => $msg->{text} + } + ); + push( + @{ $persistent_data->{him_msg} }, + { + prio => $msg->{prioritaet}, + lead => $msg->{text} + } + ); + } + } + + # selecting on user_id and train_no avoids a race condition if a user checks + # into a new train while we are fetching data for their previous journey. In + # this case, the new train would receive data from the previous journey. + $db->update( + 'in_transit', + { + real_departure => $stop->{rt_dep}, + data => $json->encode($ephemeral_data), + user_data => $json->encode($persistent_data), + }, + { + user_id => $uid, + train_id => $opt{train_id}, + checkin_station_id => $dep_eva, + checkout_station_id => $arr_eva, + } + ); +} + +sub update_departure_motis { + my ( $self, %opt ) = @_; + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + my $dep_eva = $opt{dep_eva}; + my $arr_eva = $opt{arr_eva}; + my $journey = $opt{journey}; + my $stopover = $opt{stopover}; + my $json = JSON->new; + + # selecting on user_id and train_no avoids a race condition if a user checks + # into a new train while we are fetching data for their previous journey. In + # this case, the new train would receive data from the previous journey. + $db->update( + 'in_transit', + { + real_departure => $stopover->{realtime_departure}, + }, + { + user_id => $uid, + train_id => $opt{train_id}, + checkin_station_id => $dep_eva, + checkout_station_id => $arr_eva, + } + ); +} + sub update_departure_hafas { my ( $self, %opt ) = @_; my $uid = $opt{uid}; @@ -741,12 +981,20 @@ sub update_departure_hafas { my $stop = $opt{stop}; my $json = JSON->new; + my $res_h = $db->select( 'in_transit', ['data'], { user_id => $uid } ) + ->expand->hash; + my $ephemeral_data = $res_h ? $res_h->{data} : {}; + if ( $stop->{rt_dep} ) { + $ephemeral_data->{rt} = 1; + } + # selecting on user_id and train_no avoids a race condition if a user checks # into a new train while we are fetching data for their previous journey. In # this case, the new train would receive data from the previous journey. $db->update( 'in_transit', { + data => $json->encode($ephemeral_data), real_departure => $stop->{rt_dep}, }, { @@ -800,6 +1048,146 @@ sub update_arrival { return $rows; } +sub update_arrival_dbris { + my ( $self, %opt ) = @_; + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + my $dep_eva = $opt{dep_eva}; + my $arr_eva = $opt{arr_eva}; + my $journey = $opt{journey}; + my $stop = $opt{stop}; + my $json = JSON->new; + + my $res_h = $db->select( 'in_transit', [ 'data', 'user_data' ], + { user_id => $uid } )->expand->hash; + my $ephemeral_data = $res_h ? $res_h->{data} : {}; + my $persistent_data = $res_h ? $res_h->{user_data} : {}; + + if ( $stop->{rt_arr} ) { + $ephemeral_data->{rt} = 1; + } + + $ephemeral_data->{him_msg} = []; + $persistent_data->{him_msg} = []; + for my $msg ( $journey->messages ) { + if ( not $msg->{ueberschrift} ) { + push( + @{ $ephemeral_data->{him_msg} }, + { + header => q{}, + prio => $msg->{prioritaet}, + lead => $msg->{text} + } + ); + push( + @{ $persistent_data->{him_msg} }, + { + prio => $msg->{prioritaet}, + lead => $msg->{text} + } + ); + } + } + + my @route; + for my $j_stop ( $journey->route ) { + push( + @route, + [ + $j_stop->name, + $j_stop->eva, + { + sched_arr => _epoch( $j_stop->sched_arr ), + sched_dep => _epoch( $j_stop->sched_dep ), + rt_arr => _epoch( $j_stop->rt_arr ), + rt_dep => _epoch( $j_stop->rt_dep ), + platform => $j_stop->platform, + isCancelled => $j_stop->is_cancelled, + arr_delay => $j_stop->arr_delay, + dep_delay => $j_stop->dep_delay, + load => { + FIRST => $j_stop->occupancy_first, + SECOND => $j_stop->occupancy_second + }, + lat => $j_stop->lat, + lon => $j_stop->lon, + } + ] + ); + } + + # selecting on user_id and train_no avoids a race condition if a user checks + # into a new train while we are fetching data for their previous journey. In + # this case, the new train would receive data from the previous journey. + $db->update( + 'in_transit', + { + real_arrival => $stop->{rt_arr}, + arr_platform => $stop->{platform}, + route => $json->encode( [@route] ), + data => $json->encode($ephemeral_data), + user_data => $json->encode($persistent_data), + }, + { + user_id => $uid, + train_id => $opt{train_id}, + checkin_station_id => $dep_eva, + checkout_station_id => $arr_eva, + } + ); +} + +sub update_arrival_motis { + my ( $self, %opt ) = @_; + my $uid = $opt{uid}; + my $db = $opt{db} // $self->{pg}->db; + my $dep_eva = $opt{dep_eva}; + my $arr_eva = $opt{arr_eva}; + my $journey = $opt{journey}; + my $stopover = $opt{stopover}; + my $json = JSON->new; + + my @route; + for my $journey_stopover ( $journey->stopovers ) { + push( + @route, + [ + $journey_stopover->stop->name, + $journey_stopover->stop->{eva} + // die('eva not set for stopover'), + { + sched_arr => _epoch( $journey_stopover->scheduled_arrival ), + sched_dep => + _epoch( $journey_stopover->scheduled_departure ), + rt_arr => _epoch( $journey_stopover->realtime_arrival ), + rt_dep => _epoch( $journey_stopover->realtime_departure ), + arr_delay => $journey_stopover->arrival_delay, + dep_delay => $journey_stopover->departure_delay, + lat => $journey_stopover->stop->lat, + lon => $journey_stopover->stop->lon, + } + ] + ); + } + + # selecting on user_id and train_no avoids a race condition if a user checks + # into a new train while we are fetching data for their previous journey. In + # this case, the new train would receive data from the previous journey. + $db->update( + 'in_transit', + { + real_arrival => $stopover->{realtime_arrival}, + route => $json->encode( [@route] ), + }, + { + user_id => $uid, + train_id => $opt{train_id}, + checkin_station_id => $dep_eva, + checkout_station_id => $arr_eva, + } + ); +} + sub update_arrival_hafas { my ( $self, %opt ) = @_; my $uid = $opt{uid}; @@ -810,6 +1198,16 @@ sub update_arrival_hafas { my $stop = $opt{stop}; my $json = JSON->new; + my $res_h + = $db->select( 'in_transit', [ 'data', 'route' ], { user_id => $uid } ) + ->expand->hash; + my $ephemeral_data = $res_h ? $res_h->{data} : {}; + my $old_route = $res_h ? $res_h->{route} : []; + + if ( $stop->{rt_arr} ) { + $ephemeral_data->{rt} = 1; + } + my @route; for my $j_stop ( $journey->route ) { push( @@ -835,10 +1233,6 @@ sub update_arrival_hafas { } } - my $res_h = $db->select( 'in_transit', ['route'], { user_id => $uid } ) - ->expand->hash; - my $old_route = $res_h ? $res_h->{route} : []; - for my $i ( 0 .. $#route ) { if ( $old_route->[$i] and $old_route->[$i][1] == $route[$i][1] ) { for my $k (qw(rt_arr rt_dep arr_delay dep_delay)) { @@ -853,6 +1247,7 @@ sub update_arrival_hafas { $db->update( 'in_transit', { + data => $json->encode($ephemeral_data), real_arrival => $stop->{rt_arr}, route => $json->encode( [@route] ), }, diff --git a/lib/Travelynx/Model/Journeys.pm b/lib/Travelynx/Model/Journeys.pm index 93e59e7..1662787 100755 --- a/lib/Travelynx/Model/Journeys.pm +++ b/lib/Travelynx/Model/Journeys.pm @@ -549,7 +549,7 @@ sub get { my @select = ( - qw(journey_id is_iris is_hafas backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) + qw(journey_id is_dbris is_iris is_hafas is_motis backend_name backend_id train_type train_line train_no checkin_ts sched_dep_ts real_dep_ts dep_eva dep_ds100 dep_name dep_lat dep_lon checkout_ts sched_arr_ts real_arr_ts arr_eva arr_ds100 arr_name arr_lat arr_lon cancelled edited route messages user_data visibility effective_visibility) ); my %where = ( user_id => $uid, @@ -607,8 +607,10 @@ sub get { my $ref = { id => $entry->{journey_id}, + is_dbris => $entry->{is_dbris}, is_iris => $entry->{is_iris}, is_hafas => $entry->{is_hafas}, + is_motis => $entry->{is_motis}, backend_name => $entry->{backend_name}, backend_id => $entry->{backend_id}, type => $entry->{train_type}, @@ -870,8 +872,11 @@ sub get_latest_checkout_stations { my $res = $db->select( 'journeys_str', [ - 'arr_name', 'arr_eva', 'train_id', 'backend_id', - 'backend_name', 'is_hafas' + 'arr_name', 'arr_eva', + 'arr_external_id', 'train_id', + 'backend_id', 'backend_name', + 'is_dbris', 'is_hafas', + 'is_motis' ], { user_id => $uid, @@ -893,9 +898,13 @@ sub get_latest_checkout_stations { push( @ret, { - name => $row->{arr_name}, - eva => $row->{arr_eva}, + name => $row->{arr_name}, + eva => $row->{arr_eva}, + external_id_or_eva => $row->{arr_external_id} + // $row->{arr_eva}, + dbris => $row->{is_dbris} ? $row->{backend_name} : 0, hafas => $row->{is_hafas} ? $row->{backend_name} : 0, + motis => $row->{is_motis} ? $row->{backend_name} : 0, backend_id => $row->{backend_id}, } ); @@ -1663,7 +1672,10 @@ sub compute_stats { @inconsistencies, { conflict => { - train => $journey->{type} . ' ' + train => ( + $journey->{is_motis} ? '' : $journey->{type} + ) + . ' ' . ( $journey->{line} // $journey->{no} ), arr => epoch_to_dt( $journey->{rt_arr_ts} ) ->strftime('%d.%m.%Y %H:%M'), @@ -1689,7 +1701,8 @@ sub compute_stats { $next_departure = $journey->{rt_dep_ts}; $next_id = $journey->{id}; $next_train - = $journey->{type} . ' ' . ( $journey->{line} // $journey->{no} ),; + = ( $journey->{is_motis} ? '' : $journey->{type} ) . ' ' + . ( $journey->{line} // $journey->{no} ),; } my $ret = { km_route => $km_route, @@ -1888,7 +1901,8 @@ sub get_connection_targets { ); my @destinations = $res->hashes->grep( sub { shift->{count} >= $min_count } ) - ->map( sub { shift->{dest} } )->each; + ->map( sub { shift->{dest} } ) + ->each; @destinations = $self->{stations}->get_by_evas( backend_id => $opt{backend_id}, evas => [@destinations] diff --git a/lib/Travelynx/Model/Stations.pm b/lib/Travelynx/Model/Stations.pm index f4eb096..44618ac 100644 --- a/lib/Travelynx/Model/Stations.pm +++ b/lib/Travelynx/Model/Stations.pm @@ -1,6 +1,7 @@ package Travelynx::Model::Stations; # Copyright (C) 2022 Birte Kristina Friesel +# Copyright (C) 2025 networkException <git@nwex.de> # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -25,11 +26,28 @@ sub get_backend_id { if ( $opt{hafas} and $self->{backend_id}{hafas}{ $opt{hafas} } ) { return $self->{backend_id}{hafas}{ $opt{hafas} }; } + if ( $opt{dbris} and $self->{backend_id}{dbris}{ $opt{dbris} } ) { + return $self->{backend_id}{dbris}{ $opt{dbris} }; + } + if ( $opt{motis} and $self->{backend_id}{motis}{ $opt{motis} } ) { + return $self->{backend_id}{motis}{ $opt{motis} }; + } my $db = $opt{db} // $self->{pg}->db; my $backend_id = 0; - if ( $opt{hafas} ) { + if ( $opt{dbris} ) { + $backend_id = $db->select( + 'backends', + ['id'], + { + dbris => 1, + name => $opt{dbris} + } + )->hash->{id}; + $self->{backend_id}{dbris}{ $opt{dbris} } = $backend_id; + } + elsif ( $opt{hafas} ) { $backend_id = $db->select( 'backends', ['id'], @@ -40,35 +58,40 @@ sub get_backend_id { )->hash->{id}; $self->{backend_id}{hafas}{ $opt{hafas} } = $backend_id; } + elsif ( $opt{motis} ) { + $backend_id = $db->select( + 'backends', + ['id'], + { + motis => 1, + name => $opt{motis} + } + )->hash->{id}; + $self->{backend_id}{motis}{ $opt{motis} } = $backend_id; + } return $backend_id; } -sub get_hafas_name { +sub get_backend { my ( $self, %opt ) = @_; - if ( exists $self->{hafas_name}{ $opt{backend_id} } ) { - return $self->{hafas_name}{ $opt{backend_id} }; + if ( $self->{backend_cache}{ $opt{backend_id} } ) { + return $self->{backend_cache}{ $opt{backend_id} }; } - my $db = $opt{db} // $self->{pg}->db; - my $hafas_name; + my $db = $opt{db} // $self->{pg}->db; my $ret = $db->select( 'backends', - ['name'], + '*', { - hafas => 1, - id => $opt{backend_id}, + id => $opt{backend_id}, } )->hash; - if ($ret) { - $hafas_name = $ret->{name}; - } - - $self->{hafas_name}{ $opt{backend_id} } = $hafas_name; + $self->{backend_cache}{ $opt{backend_id} } = $ret; - return $hafas_name; + return $ret; } sub get_backends { @@ -76,7 +99,8 @@ sub get_backends { $opt{db} //= $self->{pg}->db; - my $res = $opt{db}->select( 'backends', [ 'id', 'name', 'efa', 'hafas', 'iris' ] ); + my $res = $opt{db}->select( 'backends', + [ 'id', 'name', 'dbris', 'efa', 'hafas', 'iris', 'motis' ] ); my @ret; while ( my $row = $res->hash ) { @@ -85,9 +109,11 @@ sub get_backends { { id => $row->{id}, name => $row->{name}, + dbris => $row->{dbris}, efa => $row->{efa}, hafas => $row->{hafas}, iris => $row->{iris}, + motis => $row->{motis}, } ); } @@ -95,14 +121,104 @@ sub get_backends { return @ret; } +# Slow for MOTIS backends sub add_or_update { my ( $self, %opt ) = @_; my $stop = $opt{stop}; - my $loc = $stop->loc; $opt{db} //= $self->{pg}->db; $opt{backend_id} //= $self->get_backend_id(%opt); + if ( $opt{dbris} ) { + if ( + my $s = $self->get_by_eva( + $stop->eva, + db => $opt{db}, + backend_id => $opt{backend_id} + ) + ) + { + $opt{db}->update( + 'stations', + { + name => $stop->name, + lat => $stop->lat, + lon => $stop->lon, + archived => 0 + }, + { + eva => $stop->eva, + source => $opt{backend_id} + } + ); + return; + } + $opt{db}->insert( + 'stations', + { + eva => $stop->eva, + name => $stop->name, + lat => $stop->lat, + lon => $stop->lon, + source => $opt{backend_id}, + archived => 0 + } + ); + return; + } + + if ( $opt{motis} ) { + if ( + my $s = $self->get_by_external_id( + external_id => $stop->id, + db => $opt{db}, + backend_id => $opt{backend_id} + ) + ) + { + $opt{db}->update( + 'stations', + { + name => $stop->name, + lat => $stop->lat, + lon => $stop->lon, + archived => 0 + }, + { + eva => $s->{eva}, + source => $opt{backend_id} + } + ); + + # MOTIS backends do not provide a numeric ID, so we set our ID here. + $stop->{eva} = $s->{eva}; + return; + } + + my $s = $opt{db}->query( + qq { + with new_station as ( + insert into stations_external_ids (backend_id, external_id) + values (?, ?) + returning eva, backend_id + ) + + insert into stations (eva, name, lat, lon, source, archived) + values ((select eva from new_station), ?, ?, ?, (select backend_id from new_station), ?) + returning * + }, + ( + $opt{backend_id}, $stop->id, $stop->name, + $stop->lat, $stop->lon, 0, + ) + ); + + # MOTIS backends do not provide a numeric ID, so we set our ID here. + $stop->{eva} = $s->hash->{eva}; + return; + } + + my $loc = $stop->loc; if ( my $s = $self->get_by_eva( $loc->eva, @@ -137,6 +253,8 @@ sub add_or_update { archived => 0 } ); + + return; } sub add_meta { @@ -229,6 +347,27 @@ sub get_by_eva { )->hash; } +# Slow +sub get_by_external_id { + my ( $self, %opt ) = @_; + + if ( not $opt{external_id} ) { + return; + } + + $opt{db} //= $self->{pg}->db; + $opt{backend_id} //= $self->get_backend_id(%opt); + + return $opt{db}->select( + 'stations_with_external_ids', + '*', + { + external_id => $opt{external_id}, + source => $opt{backend_id}, + } + )->hash; +} + # Fast sub get_by_evas { my ( $self, %opt ) = @_; diff --git a/lib/Travelynx/Model/Traewelling.pm b/lib/Travelynx/Model/Traewelling.pm index c460b1a..608da15 100644 --- a/lib/Travelynx/Model/Traewelling.pm +++ b/lib/Travelynx/Model/Traewelling.pm @@ -224,7 +224,7 @@ sub get_pushable_accounts { join in_transit_str as i on t.user_id = i.user_id where t.push_sync = True and i.arr_eva is not null - and i.backend_id <= 1 + and i.backend_id = (select id from backends where dbris = true and name = 'bahn.de') and i.cancelled = False } ); diff --git a/lib/Travelynx/Model/Users.pm b/lib/Travelynx/Model/Users.pm index a672a38..a552633 100644 --- a/lib/Travelynx/Model/Users.pm +++ b/lib/Travelynx/Model/Users.pm @@ -418,7 +418,7 @@ sub get { . 'extract(epoch from registered_at) as registered_at_ts, ' . 'extract(epoch from last_seen) as last_seen_ts, ' . 'extract(epoch from deletion_requested) as deletion_requested_ts, ' - . 'backend_id, backend_name, efa, hafas', + . 'backend_id, backend_name, dbris, efa, hafas, motis', { id => $uid } )->hash; if ($user) { @@ -440,7 +440,7 @@ sub get { past_all => $user->{public_level} & 0x10000 ? 1 : 0, email => $user->{email}, sb_template => - 'https://dbf.finalrewind.org/{name}?rt=1&hafas={hafas}#{tt}{tn}', +'https://dbf.finalrewind.org/{name}?rt=1&dbris={dbris}&hafas={hafas}#{id_or_tttn}', registered_at => DateTime->from_epoch( epoch => $user->{registered_at_ts}, time_zone => 'Europe/Berlin' @@ -457,8 +457,10 @@ sub get { : undef, backend_id => $user->{backend_id}, backend_name => $user->{backend_name}, + backend_dbris => $user->{dbris}, backend_efa => $user->{efa}, backend_hafas => $user->{hafas}, + backend_motis => $user->{motis}, }; } return undef; diff --git a/public/service-worker.js b/public/service-worker.js index f432faa..6c57348 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,18 +1,18 @@ -const CACHE_NAME = 'static-cache-v82'; +const CACHE_NAME = 'static-cache-v87'; const FILES_TO_CACHE = [ '/favicon.ico', '/offline.html', - '/static/v82/css/light.min.css', - '/static/v82/css/dark.min.css', - '/static/v82/css/material-icons.css', - '/static/v82/fonts/MaterialIcons-Regular.woff2', - '/static/v82/fonts/MaterialIcons-Regular.woff', - '/static/v82/fonts/MaterialIcons-Regular.ttf', - '/static/v82/js/jquery-3.4.1.min.js', - '/static/v82/js/materialize.min.js', - '/static/v82/js/travelynx-actions.min.js', - '/static/v82/js/autocomplete.min.js', - '/static/v82/js/geolocation.min.js', + '/static/v87/css/light.min.css', + '/static/v87/css/dark.min.css', + '/static/v87/css/material-icons.css', + '/static/v87/fonts/MaterialIcons-Regular.woff2', + '/static/v87/fonts/MaterialIcons-Regular.woff', + '/static/v87/fonts/MaterialIcons-Regular.ttf', + '/static/v87/js/jquery-3.4.1.min.js', + '/static/v87/js/materialize.min.js', + '/static/v87/js/travelynx-actions.min.js', + '/static/v87/js/autocomplete.min.js', + '/static/v87/js/geolocation.min.js', ]; self.addEventListener('install', (evt) => { diff --git a/public/static/css/dark.min.css b/public/static/css/dark.min.css index 37da618..4abdb14 100644 --- a/public/static/css/dark.min.css +++ b/public/static/css/dark.min.css @@ -5,4 +5,4 @@ * Copyright 2014 Alfiana E. Sibuea and other contributors * Released under the MIT license * https://github.com/fians/Waves/blob/master/LICENSE - */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);transition:all 0.7s ease-out;transition-property:transform, opacity;transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{transition:none !important}.waves-circle{transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width : 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;box-shadow:none}.collapsible.popout>li{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix~.chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty~label{font-size:0.8rem;transform:translateY(-140%)}.materialboxed{display:block;cursor:zoom-in;position:relative;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #98f5ff}button:focus{outline:none;background-color:#00a9bb}label{font-size:.8rem;color:#fff}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;box-shadow:none;box-sizing:content-box;transition:box-shadow .3s, border .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #0097a7;box-shadow:0 1px 0 0 #0097a7}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#0097a7}input:not([type]):focus.valid~label,input[type=text]:not(.browser-default):focus.valid~label,input[type=password]:not(.browser-default):focus.valid~label,input[type=email]:not(.browser-default):focus.valid~label,input[type=url]:not(.browser-default):focus.valid~label,input[type=time]:not(.browser-default):focus.valid~label,input[type=date]:not(.browser-default):focus.valid~label,input[type=datetime]:not(.browser-default):focus.valid~label,input[type=datetime-local]:not(.browser-default):focus.valid~label,input[type=tel]:not(.browser-default):focus.valid~label,input[type=number]:not(.browser-default):focus.valid~label,input[type=search]:not(.browser-default):focus.valid~label,textarea.materialize-textarea:focus.valid~label{color:#388E3C}input:not([type]):focus.invalid~label,input[type=text]:not(.browser-default):focus.invalid~label,input[type=password]:not(.browser-default):focus.invalid~label,input[type=email]:not(.browser-default):focus.invalid~label,input[type=url]:not(.browser-default):focus.invalid~label,input[type=time]:not(.browser-default):focus.invalid~label,input[type=date]:not(.browser-default):focus.invalid~label,input[type=datetime]:not(.browser-default):focus.invalid~label,input[type=datetime-local]:not(.browser-default):focus.invalid~label,input[type=tel]:not(.browser-default):focus.invalid~label,input[type=number]:not(.browser-default):focus.invalid~label,input[type=search]:not(.browser-default):focus.invalid~label,textarea.materialize-textarea:focus.invalid~label{color:#D32F2F}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}.select-wrapper.valid>input.select-dropdown,input.valid:not([type]),input.valid:not([type]):focus,input[type=text].valid:not(.browser-default),input[type=text].valid:not(.browser-default):focus,input[type=password].valid:not(.browser-default),input[type=password].valid:not(.browser-default):focus,input[type=email].valid:not(.browser-default),input[type=email].valid:not(.browser-default):focus,input[type=url].valid:not(.browser-default),input[type=url].valid:not(.browser-default):focus,input[type=time].valid:not(.browser-default),input[type=time].valid:not(.browser-default):focus,input[type=date].valid:not(.browser-default),input[type=date].valid:not(.browser-default):focus,input[type=datetime].valid:not(.browser-default),input[type=datetime].valid:not(.browser-default):focus,input[type=datetime-local].valid:not(.browser-default),input[type=datetime-local].valid:not(.browser-default):focus,input[type=tel].valid:not(.browser-default),input[type=tel].valid:not(.browser-default):focus,input[type=number].valid:not(.browser-default),input[type=number].valid:not(.browser-default):focus,input[type=search].valid:not(.browser-default),input[type=search].valid:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus{border-bottom:1px solid #388E3C;box-shadow:0 1px 0 0 #388E3C}.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus,input.invalid:not([type]),input.invalid:not([type]):focus,input[type=text].invalid:not(.browser-default),input[type=text].invalid:not(.browser-default):focus,input[type=password].invalid:not(.browser-default),input[type=password].invalid:not(.browser-default):focus,input[type=email].invalid:not(.browser-default),input[type=email].invalid:not(.browser-default):focus,input[type=url].invalid:not(.browser-default),input[type=url].invalid:not(.browser-default):focus,input[type=time].invalid:not(.browser-default),input[type=time].invalid:not(.browser-default):focus,input[type=date].invalid:not(.browser-default),input[type=date].invalid:not(.browser-default):focus,input[type=datetime].invalid:not(.browser-default),input[type=datetime].invalid:not(.browser-default):focus,input[type=datetime-local].invalid:not(.browser-default),input[type=datetime-local].invalid:not(.browser-default):focus,input[type=tel].invalid:not(.browser-default),input[type=tel].invalid:not(.browser-default):focus,input[type=number].invalid:not(.browser-default),input[type=number].invalid:not(.browser-default):focus,input[type=search].invalid:not(.browser-default),input[type=search].invalid:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus{border-bottom:1px solid #D32F2F;box-shadow:0 1px 0 0 #D32F2F}.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid~.helper-text[data-error],input.valid:not([type])~.helper-text[data-success],input.valid:not([type]):focus~.helper-text[data-success],input.invalid:not([type])~.helper-text[data-error],input.invalid:not([type]):focus~.helper-text[data-error],input[type=text].valid:not(.browser-default)~.helper-text[data-success],input[type=text].invalid:not(.browser-default)~.helper-text[data-error],input[type=password].valid:not(.browser-default)~.helper-text[data-success],input[type=password].invalid:not(.browser-default)~.helper-text[data-error],input[type=email].valid:not(.browser-default)~.helper-text[data-success],input[type=email].invalid:not(.browser-default)~.helper-text[data-error],input[type=url].valid:not(.browser-default)~.helper-text[data-success],input[type=url].invalid:not(.browser-default)~.helper-text[data-error],input[type=time].valid:not(.browser-default)~.helper-text[data-success],input[type=time].invalid:not(.browser-default)~.helper-text[data-error],input[type=date].valid:not(.browser-default)~.helper-text[data-success],input[type=date].invalid:not(.browser-default)~.helper-text[data-error],input[type=datetime].valid:not(.browser-default)~.helper-text[data-success],input[type=datetime].invalid:not(.browser-default)~.helper-text[data-error],input[type=datetime-local].valid:not(.browser-default)~.helper-text[data-success],input[type=datetime-local].invalid:not(.browser-default)~.helper-text[data-error],input[type=tel].valid:not(.browser-default)~.helper-text[data-success],input[type=tel].invalid:not(.browser-default)~.helper-text[data-error],input[type=number].valid:not(.browser-default)~.helper-text[data-success],input[type=number].invalid:not(.browser-default)~.helper-text[data-error],input[type=search].valid:not(.browser-default)~.helper-text[data-success],input[type=search].invalid:not(.browser-default)~.helper-text[data-error],textarea.materialize-textarea.valid~.helper-text[data-success],textarea.materialize-textarea.valid:focus~.helper-text[data-success],textarea.materialize-textarea.invalid~.helper-text[data-error],textarea.materialize-textarea.invalid:focus~.helper-text[data-error]{color:transparent;user-select:none;pointer-events:none}.select-wrapper.valid~.helper-text:after,input.valid:not([type])~.helper-text:after,input.valid:not([type]):focus~.helper-text:after,input[type=text].valid:not(.browser-default)~.helper-text:after,input[type=text].valid:not(.browser-default):focus~.helper-text:after,input[type=password].valid:not(.browser-default)~.helper-text:after,input[type=password].valid:not(.browser-default):focus~.helper-text:after,input[type=email].valid:not(.browser-default)~.helper-text:after,input[type=email].valid:not(.browser-default):focus~.helper-text:after,input[type=url].valid:not(.browser-default)~.helper-text:after,input[type=url].valid:not(.browser-default):focus~.helper-text:after,input[type=time].valid:not(.browser-default)~.helper-text:after,input[type=time].valid:not(.browser-default):focus~.helper-text:after,input[type=date].valid:not(.browser-default)~.helper-text:after,input[type=date].valid:not(.browser-default):focus~.helper-text:after,input[type=datetime].valid:not(.browser-default)~.helper-text:after,input[type=datetime].valid:not(.browser-default):focus~.helper-text:after,input[type=datetime-local].valid:not(.browser-default)~.helper-text:after,input[type=datetime-local].valid:not(.browser-default):focus~.helper-text:after,input[type=tel].valid:not(.browser-default)~.helper-text:after,input[type=tel].valid:not(.browser-default):focus~.helper-text:after,input[type=number].valid:not(.browser-default)~.helper-text:after,input[type=number].valid:not(.browser-default):focus~.helper-text:after,input[type=search].valid:not(.browser-default)~.helper-text:after,input[type=search].valid:not(.browser-default):focus~.helper-text:after,textarea.materialize-textarea.valid~.helper-text:after,textarea.materialize-textarea.valid:focus~.helper-text:after{content:attr(data-success);color:#388E3C}.select-wrapper.invalid~.helper-text:after,input.invalid:not([type])~.helper-text:after,input.invalid:not([type]):focus~.helper-text:after,input[type=text].invalid:not(.browser-default)~.helper-text:after,input[type=text].invalid:not(.browser-default):focus~.helper-text:after,input[type=password].invalid:not(.browser-default)~.helper-text:after,input[type=password].invalid:not(.browser-default):focus~.helper-text:after,input[type=email].invalid:not(.browser-default)~.helper-text:after,input[type=email].invalid:not(.browser-default):focus~.helper-text:after,input[type=url].invalid:not(.browser-default)~.helper-text:after,input[type=url].invalid:not(.browser-default):focus~.helper-text:after,input[type=time].invalid:not(.browser-default)~.helper-text:after,input[type=time].invalid:not(.browser-default):focus~.helper-text:after,input[type=date].invalid:not(.browser-default)~.helper-text:after,input[type=date].invalid:not(.browser-default):focus~.helper-text:after,input[type=datetime].invalid:not(.browser-default)~.helper-text:after,input[type=datetime].invalid:not(.browser-default):focus~.helper-text:after,input[type=datetime-local].invalid:not(.browser-default)~.helper-text:after,input[type=datetime-local].invalid:not(.browser-default):focus~.helper-text:after,input[type=tel].invalid:not(.browser-default)~.helper-text:after,input[type=tel].invalid:not(.browser-default):focus~.helper-text:after,input[type=number].invalid:not(.browser-default)~.helper-text:after,input[type=number].invalid:not(.browser-default):focus~.helper-text:after,input[type=search].invalid:not(.browser-default)~.helper-text:after,input[type=search].invalid:not(.browser-default):focus~.helper-text:after,textarea.materialize-textarea.invalid~.helper-text:after,textarea.materialize-textarea.invalid:focus~.helper-text:after{content:attr(data-error);color:#D32F2F}.select-wrapper+label:after,input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix~label,.input-field.col .prefix~.validate~label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#fff;position:absolute;top:0;left:0;font-size:1rem;cursor:text;transition:transform .2s ease-out, color .2s ease-out;transform-origin:0% 100%;text-align:initial;transform:translateY(12px)}.input-field>label:not(.label-icon).active{transform:translateY(-14px) scale(0.8);transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{transform:translateY(-14px) scale(0.8);transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#0097a7}.input-field .prefix~input,.input-field .prefix~textarea,.input-field .prefix~label,.input-field .prefix~.validate~label,.input-field .prefix~.helper-text,.input-field .prefix~.autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix~label{margin-left:3rem}@media only screen and (max-width : 992px){.input-field .prefix~input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width : 600px){.input-field .prefix~input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default)~.mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default)~.material-icons{color:#444}.input-field input[type=search]+.label-icon{transform:none;left:1rem}.input-field input[type=search]~.mdi-navigation-close,.input-field input[type=search]~.material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;transition:.28s ease;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #fff}[type="radio"]:not(:checked)+span:after{transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #0097a7}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#0097a7}[type="radio"]:checked+span:after{transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #fff;border-radius:1px;margin-top:3px;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{transform:scale(1);border:0;border-radius:50%;box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #0097a7;border-bottom:2px solid #0097a7;transform:rotate(40deg);backface-visibility:hidden;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #0097a7;border-bottom:none;transform:rotate(90deg);backface-visibility:hidden;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;transform:rotateZ(37deg);transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #fff;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;transform:rotateZ(37deg);transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #0097a7;background-color:#0097a7;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#fff;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#0097a7;border-color:#0097a7}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#42d5e4}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#0097a7}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease}.switch label .lever:before{background-color:rgba(0,151,167,0.15)}.switch label .lever:after{background-color:#F1F1F1;box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled)~.lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus~.lever::before{transform:scale(2.4);background-color:rgba(0,151,167,0.15)}input[type=checkbox]:not(:disabled)~.lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus~.lever::before{transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #0097a7}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix~.select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix~label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup~li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#0097a7;margin-left:7px;transform-origin:50% 50%;transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#0097a7;font-size:0;transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#0097a7;transition:box-shadow .3s;-webkit-appearance:none;background-color:#0097a7;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,151,167,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#0097a7;transition:box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,151,167,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#0097a7;transition:box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(0,151,167,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #8b1014}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #8b1014}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;backface-visibility:hidden;transform:translateX(-105%)}.sidenav.right-aligned{right:0;transform:translateX(105%);left:auto;transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#fff}.sidenav li>a.btn:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#00aec1}.sidenav li>a.btn-floating:hover{background-color:#0097a7}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width : 992px){.sidenav.sidenav-fixed{transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#8b1014}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#0097a7}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{transform:rotate(130deg)}50%{transform:rotate(-5deg)}to{transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{transform:rotate(-130deg)}50%{transform:rotate(5deg)}to{transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;perspective:500px;transform-style:preserve-3d;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:#757575;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;transition:visibility 0s}.tap-target-wrapper.open .tap-target{transform:scale(1);opacity:.95;transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;transition:opacity .3s, transform .3s, visibility 0s 1s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#8b1014;box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;transform:scale(0);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{transform:scale(0);transition:transform .3s}.tap-target-wave::after{visibility:hidden;transition:opacity .3s, transform .3s, visibility 0s;z-index:-1}.tap-target-origin{top:50%;left:50%;transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;transition:opacity .3s, transform .3s;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@keyframes pulse-animation{0%{opacity:1;transform:scale(1)}50%{opacity:0;transform:scale(1.5)}100%{opacity:0;transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:flex;flex-direction:column;padding:0}.datepicker-controls{display:flex;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{flex:1 auto;background-color:#0097a7;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#0097a7}.datepicker-table td.is-selected{background-color:#0097a7;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(4,148,163,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:flex;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#0097a7;padding:0 1rem}.datepicker-clear{color:#D32F2F}@media only screen and (min-width : 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{flex-direction:row}.datepicker-date-display{flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:flex;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{flex:1 auto;background-color:#0097a7;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(0,151,167,0.25)}.timepicker-dial{transition:transform 350ms, opacity 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{transform:scale(0.8, 0.8)}.timepicker-canvas{transition:opacity 175ms}.timepicker-canvas line{stroke:#0097a7;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#0097a7}.timepicker-canvas-bg{stroke:none;fill:#0097a7}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:flex;justify-content:space-between}.timepicker-clear{color:#D32F2F}.timepicker-close{color:#0097a7}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width : 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}}.caution-color{background-color:#D32F2F}.caution-color-text{color:#D32F2F}.info-color{background-color:#263238}.info-color-text{color:#263238}.contrast-color{background-color:#fff}.contrast-color-text{color:#fff}.success-color{background-color:#388E3C}.success-color-text{color:#388E3C}html{background-color:#101010}a.unmarked{color:#fff}.white-text a{color:#eeeeff}div.targetlist{display:grid;grid-template-columns:1fr max-content;align-items:center}div.targetlist>a.nonflex{padding-left:1em;padding-top:1em;padding-bottom:1em;display:inline-block}a.tablerow{display:flex;justify-content:space-between;padding-top:1em;padding-bottom:1em;border-bottom:1px solid rgba(0,0,0,0.12)}a.tablerow .material-icons{vertical-align:bottom;margin-bottom:0.2em}a.tablerow span{display:inline-block}.pagination li a{color:#fff}.collection-item{color:#fff}.collection-item .secondary-content{color:#fff}.collection-item.disabled{color:#757575}.action-checkin,.action-checkout,.action-undo,.action-cancelled-from,.action-cancelled-to,.action-share{cursor:pointer}.config a{cursor:pointer}.navbar-fixed{z-index:1001}.brand-logo span{transition:color 1s}.brand-logo:hover .ca,.brand-logo:hover .ce{color:#a8e3fa !important}.brand-logo:hover .cb,.brand-logo:hover .cd{color:#f5c4ce !important}.wagons span{margin-right:0.5ex;color:#808080}.wagons .wagonclass{font-weight:bold;color:inherit}.wagons .wagonnum{margin-right:0;color:inherit}.wagons .checksum:before{content:"-"}h1{font-size:2.92rem;margin:1.9466666667rem 0 1.168rem 0}h2{font-size:2.28rem;margin:1.52rem 0 .912rem 0}h3{font-size:1.64rem;margin:1.0933333333rem 0 .656rem 0}.geolocation i.material-icons{font-size:16px}ul.suggestions li{padding-top:0.5rem;padding-bottom:0.5rem}.collection.departures>li,.collection.history>li{transition:background .3s;display:grid}.collection.departures>li:not(#now,.history-date-change):hover,.collection.departures>li:focus-within,.collection.history>li:not(#now,.history-date-change):hover,.collection.history>li:focus-within{background-color:#212121;outline:2px solid #039be5}.collection.departures li{grid-template-columns:10ch 10ch 1fr;align-items:center}.collection.departures li#now{background-color:#212121;padding:2rem 20px;grid-template-columns:10ch 1fr}.collection.departures li#now strong{font-weight:bold}.collection.departures li.cancelled{background-color:#702020;font-style:italic}.collection.departures li.cancelled .dep-line{background-color:transparent;border:1px solid;color:#fff}.collection.departures li.cancelled .dep-time::after{content:" ⊖";font-style:normal}.departures .dep-time{color:#fff}.departures .dep-time:focus{outline:none}.departures .dep-dest{margin-left:0.8rem}.departures .dep-dest .followee-checkin{font-size:0.9rem;display:block}.departures .dep-dest .followee-checkin i.material-icons{vertical-align:middle}.collection.history>li{display:grid;grid-template-columns:10ch 1fr;grid-template-rows:1fr}.collection.history>li a:first-child{align-self:center;text-align:center;display:flex}.collection.history>li.history-date-change{display:block;font-weight:bold}ul.route-history>li{list-style:none;position:relative;display:grid;grid-template-columns:1rem 1fr;gap:0.5rem}ul.route-history>li a{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul.route-history>li strong{font-weight:600}ul.route-history>li i.material-icons[aria-label=nach]{padding-top:0.4rem}ul.route-history>li i.material-icons[aria-label=von]{display:block;transform:rotate(-90deg);height:1rem;margin-top:0.4rem}ul.route-history>li::before{content:'';background:#fff;position:absolute;width:2px;left:calc( (1rem - 2px) / 2);bottom:0;top:0}ul.route-history>li:first-of-type::before{top:1.3rem}ul.route-history>li:last-of-type::before{bottom:unset;height:0.5rem}.dep-line{text-align:center;padding:.2rem;color:white;background:#424242;border-radius:.2rem;display:inline-block;font-weight:600;line-height:1;height:fit-content;width:fit-content;min-width:6ch;margin:0 auto}.dep-line.Bus,.dep-line.RUF,.dep-line.AST{background-color:#a3167e;border-radius:5rem;padding:.2rem .5rem}.dep-line.STR,.dep-line.Tram,.dep-line.Str,.dep-line.Strb,.dep-line.STB{background-color:#c5161c;border-radius:5rem;padding:.2rem .5rem}.dep-line.S,.dep-line.RS,.dep-line.RER,.dep-line.SKW{background-color:#008d4f;border-radius:5rem;padding:.2rem .5rem}.dep-line.U,.dep-line.M{background-color:#014e8d;border-radius:5rem;padding:.2rem .5rem}.dep-line.RE,.dep-line.IRE,.dep-line.REX{background-color:#ff4f00}.dep-line.RB,.dep-line.MEX,.dep-line.TER,.dep-line.R{background-color:#1f4a87}.dep-line.IC,.dep-line.ICE,.dep-line.EC,.dep-line.ECE,.dep-line.D,.dep-line.IR,.dep-line.TGV,.dep-line.OGV,.dep-line.EST,.dep-line.TLK,.dep-line.EIC{background-color:#ff0404;font-weight:900;font-style:italic;padding:.2rem}.dep-line.RJ,.dep-line.RJX{background-color:#c63131}.dep-line.NJ,.dep-line.EN{background-color:#29255b}.dep-line.WB{background-color:#2e85ce}.dep-line.FLX{background-color:#71d800;color:black}.departures.connections li{grid-template-columns:15ch 10ch 1fr}.departures.connections .connect-platform-wrapper{text-align:center}.departures.connections .connect-platform-wrapper span{display:block}@media screen and (max-width: 600px){.collection.departures li{grid-template-columns:10ch 1fr}.collection.departures li .dep-line,.collection.departures li .dep-time,.collection.departures li .connect-platform-wrapper{grid-column:1;text-align:center}.collection.departures li .dep-dest{grid-column:2;grid-row:1 / span 2}.departures.connections li{grid-template-columns:15ch 1fr}.departures.connections li .connect-platform-wrapper span{display:inline-block}}a.timeline-link{padding-top:1ex;padding-bottom:1ex}.progress{background-color:#424242}.progress>.determinate{background-color:#2196F3}html{background-color:#101010}input[type=email],input[type=text],input[type=password],textarea{color:#fff} + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);transition:all 0.7s ease-out;transition-property:transform, opacity;transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{transition:none !important}.waves-circle{transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width : 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;box-shadow:none}.collapsible.popout>li{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix~.chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty~label{font-size:0.8rem;transform:translateY(-140%)}.materialboxed{display:block;cursor:zoom-in;position:relative;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #98f5ff}button:focus{outline:none;background-color:#00a9bb}label{font-size:.8rem;color:#fff}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;box-shadow:none;box-sizing:content-box;transition:box-shadow .3s, border .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #0097a7;box-shadow:0 1px 0 0 #0097a7}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#0097a7}input:not([type]):focus.valid~label,input[type=text]:not(.browser-default):focus.valid~label,input[type=password]:not(.browser-default):focus.valid~label,input[type=email]:not(.browser-default):focus.valid~label,input[type=url]:not(.browser-default):focus.valid~label,input[type=time]:not(.browser-default):focus.valid~label,input[type=date]:not(.browser-default):focus.valid~label,input[type=datetime]:not(.browser-default):focus.valid~label,input[type=datetime-local]:not(.browser-default):focus.valid~label,input[type=tel]:not(.browser-default):focus.valid~label,input[type=number]:not(.browser-default):focus.valid~label,input[type=search]:not(.browser-default):focus.valid~label,textarea.materialize-textarea:focus.valid~label{color:#388E3C}input:not([type]):focus.invalid~label,input[type=text]:not(.browser-default):focus.invalid~label,input[type=password]:not(.browser-default):focus.invalid~label,input[type=email]:not(.browser-default):focus.invalid~label,input[type=url]:not(.browser-default):focus.invalid~label,input[type=time]:not(.browser-default):focus.invalid~label,input[type=date]:not(.browser-default):focus.invalid~label,input[type=datetime]:not(.browser-default):focus.invalid~label,input[type=datetime-local]:not(.browser-default):focus.invalid~label,input[type=tel]:not(.browser-default):focus.invalid~label,input[type=number]:not(.browser-default):focus.invalid~label,input[type=search]:not(.browser-default):focus.invalid~label,textarea.materialize-textarea:focus.invalid~label{color:#D32F2F}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}.select-wrapper.valid>input.select-dropdown,input.valid:not([type]),input.valid:not([type]):focus,input[type=text].valid:not(.browser-default),input[type=text].valid:not(.browser-default):focus,input[type=password].valid:not(.browser-default),input[type=password].valid:not(.browser-default):focus,input[type=email].valid:not(.browser-default),input[type=email].valid:not(.browser-default):focus,input[type=url].valid:not(.browser-default),input[type=url].valid:not(.browser-default):focus,input[type=time].valid:not(.browser-default),input[type=time].valid:not(.browser-default):focus,input[type=date].valid:not(.browser-default),input[type=date].valid:not(.browser-default):focus,input[type=datetime].valid:not(.browser-default),input[type=datetime].valid:not(.browser-default):focus,input[type=datetime-local].valid:not(.browser-default),input[type=datetime-local].valid:not(.browser-default):focus,input[type=tel].valid:not(.browser-default),input[type=tel].valid:not(.browser-default):focus,input[type=number].valid:not(.browser-default),input[type=number].valid:not(.browser-default):focus,input[type=search].valid:not(.browser-default),input[type=search].valid:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus{border-bottom:1px solid #388E3C;box-shadow:0 1px 0 0 #388E3C}.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus,input.invalid:not([type]),input.invalid:not([type]):focus,input[type=text].invalid:not(.browser-default),input[type=text].invalid:not(.browser-default):focus,input[type=password].invalid:not(.browser-default),input[type=password].invalid:not(.browser-default):focus,input[type=email].invalid:not(.browser-default),input[type=email].invalid:not(.browser-default):focus,input[type=url].invalid:not(.browser-default),input[type=url].invalid:not(.browser-default):focus,input[type=time].invalid:not(.browser-default),input[type=time].invalid:not(.browser-default):focus,input[type=date].invalid:not(.browser-default),input[type=date].invalid:not(.browser-default):focus,input[type=datetime].invalid:not(.browser-default),input[type=datetime].invalid:not(.browser-default):focus,input[type=datetime-local].invalid:not(.browser-default),input[type=datetime-local].invalid:not(.browser-default):focus,input[type=tel].invalid:not(.browser-default),input[type=tel].invalid:not(.browser-default):focus,input[type=number].invalid:not(.browser-default),input[type=number].invalid:not(.browser-default):focus,input[type=search].invalid:not(.browser-default),input[type=search].invalid:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus{border-bottom:1px solid #D32F2F;box-shadow:0 1px 0 0 #D32F2F}.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid~.helper-text[data-error],input.valid:not([type])~.helper-text[data-success],input.valid:not([type]):focus~.helper-text[data-success],input.invalid:not([type])~.helper-text[data-error],input.invalid:not([type]):focus~.helper-text[data-error],input[type=text].valid:not(.browser-default)~.helper-text[data-success],input[type=text].invalid:not(.browser-default)~.helper-text[data-error],input[type=password].valid:not(.browser-default)~.helper-text[data-success],input[type=password].invalid:not(.browser-default)~.helper-text[data-error],input[type=email].valid:not(.browser-default)~.helper-text[data-success],input[type=email].invalid:not(.browser-default)~.helper-text[data-error],input[type=url].valid:not(.browser-default)~.helper-text[data-success],input[type=url].invalid:not(.browser-default)~.helper-text[data-error],input[type=time].valid:not(.browser-default)~.helper-text[data-success],input[type=time].invalid:not(.browser-default)~.helper-text[data-error],input[type=date].valid:not(.browser-default)~.helper-text[data-success],input[type=date].invalid:not(.browser-default)~.helper-text[data-error],input[type=datetime].valid:not(.browser-default)~.helper-text[data-success],input[type=datetime].invalid:not(.browser-default)~.helper-text[data-error],input[type=datetime-local].valid:not(.browser-default)~.helper-text[data-success],input[type=datetime-local].invalid:not(.browser-default)~.helper-text[data-error],input[type=tel].valid:not(.browser-default)~.helper-text[data-success],input[type=tel].invalid:not(.browser-default)~.helper-text[data-error],input[type=number].valid:not(.browser-default)~.helper-text[data-success],input[type=number].invalid:not(.browser-default)~.helper-text[data-error],input[type=search].valid:not(.browser-default)~.helper-text[data-success],input[type=search].invalid:not(.browser-default)~.helper-text[data-error],textarea.materialize-textarea.valid~.helper-text[data-success],textarea.materialize-textarea.valid:focus~.helper-text[data-success],textarea.materialize-textarea.invalid~.helper-text[data-error],textarea.materialize-textarea.invalid:focus~.helper-text[data-error]{color:transparent;user-select:none;pointer-events:none}.select-wrapper.valid~.helper-text:after,input.valid:not([type])~.helper-text:after,input.valid:not([type]):focus~.helper-text:after,input[type=text].valid:not(.browser-default)~.helper-text:after,input[type=text].valid:not(.browser-default):focus~.helper-text:after,input[type=password].valid:not(.browser-default)~.helper-text:after,input[type=password].valid:not(.browser-default):focus~.helper-text:after,input[type=email].valid:not(.browser-default)~.helper-text:after,input[type=email].valid:not(.browser-default):focus~.helper-text:after,input[type=url].valid:not(.browser-default)~.helper-text:after,input[type=url].valid:not(.browser-default):focus~.helper-text:after,input[type=time].valid:not(.browser-default)~.helper-text:after,input[type=time].valid:not(.browser-default):focus~.helper-text:after,input[type=date].valid:not(.browser-default)~.helper-text:after,input[type=date].valid:not(.browser-default):focus~.helper-text:after,input[type=datetime].valid:not(.browser-default)~.helper-text:after,input[type=datetime].valid:not(.browser-default):focus~.helper-text:after,input[type=datetime-local].valid:not(.browser-default)~.helper-text:after,input[type=datetime-local].valid:not(.browser-default):focus~.helper-text:after,input[type=tel].valid:not(.browser-default)~.helper-text:after,input[type=tel].valid:not(.browser-default):focus~.helper-text:after,input[type=number].valid:not(.browser-default)~.helper-text:after,input[type=number].valid:not(.browser-default):focus~.helper-text:after,input[type=search].valid:not(.browser-default)~.helper-text:after,input[type=search].valid:not(.browser-default):focus~.helper-text:after,textarea.materialize-textarea.valid~.helper-text:after,textarea.materialize-textarea.valid:focus~.helper-text:after{content:attr(data-success);color:#388E3C}.select-wrapper.invalid~.helper-text:after,input.invalid:not([type])~.helper-text:after,input.invalid:not([type]):focus~.helper-text:after,input[type=text].invalid:not(.browser-default)~.helper-text:after,input[type=text].invalid:not(.browser-default):focus~.helper-text:after,input[type=password].invalid:not(.browser-default)~.helper-text:after,input[type=password].invalid:not(.browser-default):focus~.helper-text:after,input[type=email].invalid:not(.browser-default)~.helper-text:after,input[type=email].invalid:not(.browser-default):focus~.helper-text:after,input[type=url].invalid:not(.browser-default)~.helper-text:after,input[type=url].invalid:not(.browser-default):focus~.helper-text:after,input[type=time].invalid:not(.browser-default)~.helper-text:after,input[type=time].invalid:not(.browser-default):focus~.helper-text:after,input[type=date].invalid:not(.browser-default)~.helper-text:after,input[type=date].invalid:not(.browser-default):focus~.helper-text:after,input[type=datetime].invalid:not(.browser-default)~.helper-text:after,input[type=datetime].invalid:not(.browser-default):focus~.helper-text:after,input[type=datetime-local].invalid:not(.browser-default)~.helper-text:after,input[type=datetime-local].invalid:not(.browser-default):focus~.helper-text:after,input[type=tel].invalid:not(.browser-default)~.helper-text:after,input[type=tel].invalid:not(.browser-default):focus~.helper-text:after,input[type=number].invalid:not(.browser-default)~.helper-text:after,input[type=number].invalid:not(.browser-default):focus~.helper-text:after,input[type=search].invalid:not(.browser-default)~.helper-text:after,input[type=search].invalid:not(.browser-default):focus~.helper-text:after,textarea.materialize-textarea.invalid~.helper-text:after,textarea.materialize-textarea.invalid:focus~.helper-text:after{content:attr(data-error);color:#D32F2F}.select-wrapper+label:after,input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix~label,.input-field.col .prefix~.validate~label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#fff;position:absolute;top:0;left:0;font-size:1rem;cursor:text;transition:transform .2s ease-out, color .2s ease-out;transform-origin:0% 100%;text-align:initial;transform:translateY(12px)}.input-field>label:not(.label-icon).active{transform:translateY(-14px) scale(0.8);transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{transform:translateY(-14px) scale(0.8);transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#0097a7}.input-field .prefix~input,.input-field .prefix~textarea,.input-field .prefix~label,.input-field .prefix~.validate~label,.input-field .prefix~.helper-text,.input-field .prefix~.autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix~label{margin-left:3rem}@media only screen and (max-width : 992px){.input-field .prefix~input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width : 600px){.input-field .prefix~input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default)~.mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default)~.material-icons{color:#444}.input-field input[type=search]+.label-icon{transform:none;left:1rem}.input-field input[type=search]~.mdi-navigation-close,.input-field input[type=search]~.material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;transition:.28s ease;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #fff}[type="radio"]:not(:checked)+span:after{transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #0097a7}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#0097a7}[type="radio"]:checked+span:after{transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #fff;border-radius:1px;margin-top:3px;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{transform:scale(1);border:0;border-radius:50%;box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #0097a7;border-bottom:2px solid #0097a7;transform:rotate(40deg);backface-visibility:hidden;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #0097a7;border-bottom:none;transform:rotate(90deg);backface-visibility:hidden;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;transform:rotateZ(37deg);transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #fff;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;transform:rotateZ(37deg);transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #0097a7;background-color:#0097a7;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#fff;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#0097a7;border-color:#0097a7}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#42d5e4}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#0097a7}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease}.switch label .lever:before{background-color:rgba(0,151,167,0.15)}.switch label .lever:after{background-color:#F1F1F1;box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled)~.lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus~.lever::before{transform:scale(2.4);background-color:rgba(0,151,167,0.15)}input[type=checkbox]:not(:disabled)~.lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus~.lever::before{transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #0097a7}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix~.select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix~label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup~li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#0097a7;margin-left:7px;transform-origin:50% 50%;transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#0097a7;font-size:0;transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#0097a7;transition:box-shadow .3s;-webkit-appearance:none;background-color:#0097a7;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,151,167,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#0097a7;transition:box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,151,167,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#0097a7;transition:box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(0,151,167,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #8b1014}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #8b1014}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;backface-visibility:hidden;transform:translateX(-105%)}.sidenav.right-aligned{right:0;transform:translateX(105%);left:auto;transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#fff}.sidenav li>a.btn:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#00aec1}.sidenav li>a.btn-floating:hover{background-color:#0097a7}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width : 992px){.sidenav.sidenav-fixed{transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#8b1014}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#0097a7}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{transform:rotate(130deg)}50%{transform:rotate(-5deg)}to{transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{transform:rotate(-130deg)}50%{transform:rotate(5deg)}to{transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;perspective:500px;transform-style:preserve-3d;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:#757575;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;transition:visibility 0s}.tap-target-wrapper.open .tap-target{transform:scale(1);opacity:.95;transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;transition:opacity .3s, transform .3s, visibility 0s 1s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#8b1014;box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;transform:scale(0);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{transform:scale(0);transition:transform .3s}.tap-target-wave::after{visibility:hidden;transition:opacity .3s, transform .3s, visibility 0s;z-index:-1}.tap-target-origin{top:50%;left:50%;transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;transition:opacity .3s, transform .3s;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@keyframes pulse-animation{0%{opacity:1;transform:scale(1)}50%{opacity:0;transform:scale(1.5)}100%{opacity:0;transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:flex;flex-direction:column;padding:0}.datepicker-controls{display:flex;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{flex:1 auto;background-color:#0097a7;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#0097a7}.datepicker-table td.is-selected{background-color:#0097a7;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(4,148,163,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:flex;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#0097a7;padding:0 1rem}.datepicker-clear{color:#D32F2F}@media only screen and (min-width : 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{flex-direction:row}.datepicker-date-display{flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:flex;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{flex:1 auto;background-color:#0097a7;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(0,151,167,0.25)}.timepicker-dial{transition:transform 350ms, opacity 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{transform:scale(0.8, 0.8)}.timepicker-canvas{transition:opacity 175ms}.timepicker-canvas line{stroke:#0097a7;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#0097a7}.timepicker-canvas-bg{stroke:none;fill:#0097a7}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:flex;justify-content:space-between}.timepicker-clear{color:#D32F2F}.timepicker-close{color:#0097a7}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width : 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}}.caution-color{background-color:#D32F2F}.caution-color-text{color:#D32F2F}.info-color{background-color:#263238}.info-color-text{color:#263238}.contrast-color{background-color:#fff}.contrast-color-text{color:#fff}.success-color{background-color:#388E3C}.success-color-text{color:#388E3C}html{background-color:#101010}a.unmarked{color:#fff}.white-text a{color:#eeeeff}div.targetlist{display:grid;grid-template-columns:1fr max-content;align-items:center}div.targetlist>a.nonflex{padding-left:1em;padding-top:1em;padding-bottom:1em;display:inline-block}a.tablerow{display:flex;justify-content:space-between;padding-top:1em;padding-bottom:1em;border-bottom:1px solid rgba(0,0,0,0.12)}a.tablerow .material-icons{vertical-align:bottom;margin-bottom:0.2em}a.tablerow span{display:inline-block}.pagination li a{color:#fff}.collection-item{color:#fff}.collection-item .secondary-content{color:#fff}.collection-item.disabled{color:#757575}.action-checkin,.action-checkout,.action-undo,.action-cancelled-from,.action-cancelled-to,.action-share{cursor:pointer}.config a{cursor:pointer}.navbar-fixed{z-index:1001}.brand-logo span{transition:color 1s}.brand-logo:hover .ca,.brand-logo:hover .ce{color:#a8e3fa !important}.brand-logo:hover .cb,.brand-logo:hover .cd{color:#f5c4ce !important}.wagons span{margin-right:0.5ex;color:#808080}.wagons .wagonclass{font-weight:bold;color:inherit}.wagons .wagonnum{margin-right:0;color:inherit}.wagons .checksum:before{content:"-"}h1{font-size:2.92rem;margin:1.9466666667rem 0 1.168rem 0}h2{font-size:2.28rem;margin:1.52rem 0 .912rem 0}h3{font-size:1.64rem;margin:1.0933333333rem 0 .656rem 0}.geolocation i.material-icons{font-size:16px}ul.suggestions li{padding-top:0.5rem;padding-bottom:0.5rem}.collection.departures>li,.collection.history>li{transition:background .3s;display:grid}.collection.departures>li:not(#now,.history-date-change):hover,.collection.departures>li:focus-within,.collection.history>li:not(#now,.history-date-change):hover,.collection.history>li:focus-within{background-color:#212121;outline:2px solid #039be5}.collection.departures li{grid-template-columns:10ch 10ch 1fr;align-items:center}.collection.departures li#now{background-color:#212121;padding:2rem 20px;grid-template-columns:10ch 1fr}.collection.departures li#now strong{font-weight:bold}.collection.departures li.cancelled{background-color:#702020;font-style:italic}.collection.departures li.cancelled .dep-line{background-color:transparent;border:1px solid;color:#fff}.collection.departures li.cancelled .dep-time::after{content:" ⊖";font-style:normal}.departures .dep-time{color:#fff}.departures .dep-time:focus{outline:none}.departures .dep-dest{margin-left:0.8rem}.departures .dep-dest .followee-checkin{font-size:0.9rem;display:block}.departures .dep-dest .followee-checkin i.material-icons{vertical-align:middle}.collection.history>li{display:grid;grid-template-columns:10ch 1fr;grid-template-rows:1fr}.collection.history>li a:first-child{align-self:center;text-align:center;display:flex}.collection.history>li.history-date-change{display:block;font-weight:bold}ul.route-history>li{list-style:none;position:relative;display:grid;grid-template-columns:1rem 1fr;gap:0.5rem}ul.route-history>li a{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul.route-history>li strong{font-weight:600}ul.route-history>li i.material-icons[aria-label=nach]{padding-top:0.4rem}ul.route-history>li i.material-icons[aria-label=von]{display:block;transform:rotate(-90deg);height:1rem;margin-top:0.4rem}ul.route-history>li::before{content:'';background:#fff;position:absolute;width:2px;left:calc( (1rem - 2px) / 2);bottom:0;top:0}ul.route-history>li:first-of-type::before{top:1.3rem}ul.route-history>li:last-of-type::before{bottom:unset;height:0.5rem}.dep-line{text-align:center;padding:.2rem;color:white;background:#424242;border-radius:.2rem;display:inline-block;font-weight:600;line-height:1;height:fit-content;width:fit-content;min-width:6ch;margin:0 auto}.dep-line.Bus,.dep-line.BUS,.dep-line.RUF,.dep-line.AST{background-color:#a3167e;border-radius:5rem;padding:.2rem .5rem}.dep-line.STR,.dep-line.Tram,.dep-line.TRAM,.dep-line.Str,.dep-line.Strb,.dep-line.STB{background-color:#c5161c;border-radius:5rem;padding:.2rem .5rem}.dep-line.S,.dep-line.RS,.dep-line.RER,.dep-line.SKW,.dep-line.METRO{background-color:#008d4f;border-radius:5rem;padding:.2rem .5rem}.dep-line.U,.dep-line.M,.dep-line.SUBWAY{background-color:#014e8d;border-radius:5rem;padding:.2rem .5rem}.dep-line.RE,.dep-line.IRE,.dep-line.REX,.dep-line.REGIONAL_FAST_RAIL{background-color:#ff4f00}.dep-line.RB,.dep-line.MEX,.dep-line.TER,.dep-line.R,.dep-line.REGIONAL_RAIL{background-color:#1f4a87}.dep-line.IC,.dep-line.ICE,.dep-line.EC,.dep-line.ECE,.dep-line.D,.dep-line.IR,.dep-line.TGV,.dep-line.OGV,.dep-line.EST,.dep-line.TLK,.dep-line.EIC,.dep-line.HIGHSPEED_RAIL,.dep-line.LONG_DISTANCE{background-color:#ff0404;font-weight:900;font-style:italic;padding:.2rem}.dep-line.RJ,.dep-line.RJX{background-color:#c63131}.dep-line.NJ,.dep-line.EN,.dep-line.NIGHT_RAIL{background-color:#29255b}.dep-line.WB{background-color:#2e85ce}.dep-line.FLX{background-color:#71d800;color:black}.departures.connections li{grid-template-columns:15ch 10ch 1fr}.departures.connections .connect-platform-wrapper{text-align:center}.departures.connections .connect-platform-wrapper span{display:block}.status-card-progress-annot{padding-bottom:2ex;border-bottom:2px dashed #808080}.timeline-in-transit .status-card-progress-annot{border-bottom:none}@media screen and (max-width: 600px){.collection.departures li{grid-template-columns:10ch 1fr}.collection.departures li .dep-line,.collection.departures li .dep-time,.collection.departures li .connect-platform-wrapper{grid-column:1;text-align:center}.collection.departures li .dep-dest{grid-column:2;grid-row:1 / span 2}.departures.connections li{grid-template-columns:15ch 1fr}.departures.connections li .connect-platform-wrapper span{display:inline-block}}a.timeline-link{padding-top:1ex;padding-bottom:1ex}.progress{background-color:#424242}.progress>.determinate{background-color:#2196F3}html{background-color:#101010}input[type=email],input[type=text],input[type=password],textarea{color:#fff} diff --git a/public/static/css/light.min.css b/public/static/css/light.min.css index 9170d57..83b087e 100644 --- a/public/static/css/light.min.css +++ b/public/static/css/light.min.css @@ -5,4 +5,4 @@ * Copyright 2014 Alfiana E. Sibuea and other contributors * Released under the MIT license * https://github.com/fians/Waves/blob/master/LICENSE - */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);transition:all 0.7s ease-out;transition-property:transform, opacity;transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{transition:none !important}.waves-circle{transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width : 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;box-shadow:none}.collapsible.popout>li{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix~.chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty~label{font-size:0.8rem;transform:translateY(-140%)}.materialboxed{display:block;cursor:zoom-in;position:relative;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#000}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;box-shadow:none;box-sizing:content-box;transition:box-shadow .3s, border .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid~label,input[type=text]:not(.browser-default):focus.valid~label,input[type=password]:not(.browser-default):focus.valid~label,input[type=email]:not(.browser-default):focus.valid~label,input[type=url]:not(.browser-default):focus.valid~label,input[type=time]:not(.browser-default):focus.valid~label,input[type=date]:not(.browser-default):focus.valid~label,input[type=datetime]:not(.browser-default):focus.valid~label,input[type=datetime-local]:not(.browser-default):focus.valid~label,input[type=tel]:not(.browser-default):focus.valid~label,input[type=number]:not(.browser-default):focus.valid~label,input[type=search]:not(.browser-default):focus.valid~label,textarea.materialize-textarea:focus.valid~label{color:#4CAF50}input:not([type]):focus.invalid~label,input[type=text]:not(.browser-default):focus.invalid~label,input[type=password]:not(.browser-default):focus.invalid~label,input[type=email]:not(.browser-default):focus.invalid~label,input[type=url]:not(.browser-default):focus.invalid~label,input[type=time]:not(.browser-default):focus.invalid~label,input[type=date]:not(.browser-default):focus.invalid~label,input[type=datetime]:not(.browser-default):focus.invalid~label,input[type=datetime-local]:not(.browser-default):focus.invalid~label,input[type=tel]:not(.browser-default):focus.invalid~label,input[type=number]:not(.browser-default):focus.invalid~label,input[type=search]:not(.browser-default):focus.invalid~label,textarea.materialize-textarea:focus.invalid~label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}.select-wrapper.valid>input.select-dropdown,input.valid:not([type]),input.valid:not([type]):focus,input[type=text].valid:not(.browser-default),input[type=text].valid:not(.browser-default):focus,input[type=password].valid:not(.browser-default),input[type=password].valid:not(.browser-default):focus,input[type=email].valid:not(.browser-default),input[type=email].valid:not(.browser-default):focus,input[type=url].valid:not(.browser-default),input[type=url].valid:not(.browser-default):focus,input[type=time].valid:not(.browser-default),input[type=time].valid:not(.browser-default):focus,input[type=date].valid:not(.browser-default),input[type=date].valid:not(.browser-default):focus,input[type=datetime].valid:not(.browser-default),input[type=datetime].valid:not(.browser-default):focus,input[type=datetime-local].valid:not(.browser-default),input[type=datetime-local].valid:not(.browser-default):focus,input[type=tel].valid:not(.browser-default),input[type=tel].valid:not(.browser-default):focus,input[type=number].valid:not(.browser-default),input[type=number].valid:not(.browser-default):focus,input[type=search].valid:not(.browser-default),input[type=search].valid:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus{border-bottom:1px solid #4CAF50;box-shadow:0 1px 0 0 #4CAF50}.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus,input.invalid:not([type]),input.invalid:not([type]):focus,input[type=text].invalid:not(.browser-default),input[type=text].invalid:not(.browser-default):focus,input[type=password].invalid:not(.browser-default),input[type=password].invalid:not(.browser-default):focus,input[type=email].invalid:not(.browser-default),input[type=email].invalid:not(.browser-default):focus,input[type=url].invalid:not(.browser-default),input[type=url].invalid:not(.browser-default):focus,input[type=time].invalid:not(.browser-default),input[type=time].invalid:not(.browser-default):focus,input[type=date].invalid:not(.browser-default),input[type=date].invalid:not(.browser-default):focus,input[type=datetime].invalid:not(.browser-default),input[type=datetime].invalid:not(.browser-default):focus,input[type=datetime-local].invalid:not(.browser-default),input[type=datetime-local].invalid:not(.browser-default):focus,input[type=tel].invalid:not(.browser-default),input[type=tel].invalid:not(.browser-default):focus,input[type=number].invalid:not(.browser-default),input[type=number].invalid:not(.browser-default):focus,input[type=search].invalid:not(.browser-default),input[type=search].invalid:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus{border-bottom:1px solid #F44336;box-shadow:0 1px 0 0 #F44336}.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid~.helper-text[data-error],input.valid:not([type])~.helper-text[data-success],input.valid:not([type]):focus~.helper-text[data-success],input.invalid:not([type])~.helper-text[data-error],input.invalid:not([type]):focus~.helper-text[data-error],input[type=text].valid:not(.browser-default)~.helper-text[data-success],input[type=text].invalid:not(.browser-default)~.helper-text[data-error],input[type=password].valid:not(.browser-default)~.helper-text[data-success],input[type=password].invalid:not(.browser-default)~.helper-text[data-error],input[type=email].valid:not(.browser-default)~.helper-text[data-success],input[type=email].invalid:not(.browser-default)~.helper-text[data-error],input[type=url].valid:not(.browser-default)~.helper-text[data-success],input[type=url].invalid:not(.browser-default)~.helper-text[data-error],input[type=time].valid:not(.browser-default)~.helper-text[data-success],input[type=time].invalid:not(.browser-default)~.helper-text[data-error],input[type=date].valid:not(.browser-default)~.helper-text[data-success],input[type=date].invalid:not(.browser-default)~.helper-text[data-error],input[type=datetime].valid:not(.browser-default)~.helper-text[data-success],input[type=datetime].invalid:not(.browser-default)~.helper-text[data-error],input[type=datetime-local].valid:not(.browser-default)~.helper-text[data-success],input[type=datetime-local].invalid:not(.browser-default)~.helper-text[data-error],input[type=tel].valid:not(.browser-default)~.helper-text[data-success],input[type=tel].invalid:not(.browser-default)~.helper-text[data-error],input[type=number].valid:not(.browser-default)~.helper-text[data-success],input[type=number].invalid:not(.browser-default)~.helper-text[data-error],input[type=search].valid:not(.browser-default)~.helper-text[data-success],input[type=search].invalid:not(.browser-default)~.helper-text[data-error],textarea.materialize-textarea.valid~.helper-text[data-success],textarea.materialize-textarea.valid:focus~.helper-text[data-success],textarea.materialize-textarea.invalid~.helper-text[data-error],textarea.materialize-textarea.invalid:focus~.helper-text[data-error]{color:transparent;user-select:none;pointer-events:none}.select-wrapper.valid~.helper-text:after,input.valid:not([type])~.helper-text:after,input.valid:not([type]):focus~.helper-text:after,input[type=text].valid:not(.browser-default)~.helper-text:after,input[type=text].valid:not(.browser-default):focus~.helper-text:after,input[type=password].valid:not(.browser-default)~.helper-text:after,input[type=password].valid:not(.browser-default):focus~.helper-text:after,input[type=email].valid:not(.browser-default)~.helper-text:after,input[type=email].valid:not(.browser-default):focus~.helper-text:after,input[type=url].valid:not(.browser-default)~.helper-text:after,input[type=url].valid:not(.browser-default):focus~.helper-text:after,input[type=time].valid:not(.browser-default)~.helper-text:after,input[type=time].valid:not(.browser-default):focus~.helper-text:after,input[type=date].valid:not(.browser-default)~.helper-text:after,input[type=date].valid:not(.browser-default):focus~.helper-text:after,input[type=datetime].valid:not(.browser-default)~.helper-text:after,input[type=datetime].valid:not(.browser-default):focus~.helper-text:after,input[type=datetime-local].valid:not(.browser-default)~.helper-text:after,input[type=datetime-local].valid:not(.browser-default):focus~.helper-text:after,input[type=tel].valid:not(.browser-default)~.helper-text:after,input[type=tel].valid:not(.browser-default):focus~.helper-text:after,input[type=number].valid:not(.browser-default)~.helper-text:after,input[type=number].valid:not(.browser-default):focus~.helper-text:after,input[type=search].valid:not(.browser-default)~.helper-text:after,input[type=search].valid:not(.browser-default):focus~.helper-text:after,textarea.materialize-textarea.valid~.helper-text:after,textarea.materialize-textarea.valid:focus~.helper-text:after{content:attr(data-success);color:#4CAF50}.select-wrapper.invalid~.helper-text:after,input.invalid:not([type])~.helper-text:after,input.invalid:not([type]):focus~.helper-text:after,input[type=text].invalid:not(.browser-default)~.helper-text:after,input[type=text].invalid:not(.browser-default):focus~.helper-text:after,input[type=password].invalid:not(.browser-default)~.helper-text:after,input[type=password].invalid:not(.browser-default):focus~.helper-text:after,input[type=email].invalid:not(.browser-default)~.helper-text:after,input[type=email].invalid:not(.browser-default):focus~.helper-text:after,input[type=url].invalid:not(.browser-default)~.helper-text:after,input[type=url].invalid:not(.browser-default):focus~.helper-text:after,input[type=time].invalid:not(.browser-default)~.helper-text:after,input[type=time].invalid:not(.browser-default):focus~.helper-text:after,input[type=date].invalid:not(.browser-default)~.helper-text:after,input[type=date].invalid:not(.browser-default):focus~.helper-text:after,input[type=datetime].invalid:not(.browser-default)~.helper-text:after,input[type=datetime].invalid:not(.browser-default):focus~.helper-text:after,input[type=datetime-local].invalid:not(.browser-default)~.helper-text:after,input[type=datetime-local].invalid:not(.browser-default):focus~.helper-text:after,input[type=tel].invalid:not(.browser-default)~.helper-text:after,input[type=tel].invalid:not(.browser-default):focus~.helper-text:after,input[type=number].invalid:not(.browser-default)~.helper-text:after,input[type=number].invalid:not(.browser-default):focus~.helper-text:after,input[type=search].invalid:not(.browser-default)~.helper-text:after,input[type=search].invalid:not(.browser-default):focus~.helper-text:after,textarea.materialize-textarea.invalid~.helper-text:after,textarea.materialize-textarea.invalid:focus~.helper-text:after{content:attr(data-error);color:#F44336}.select-wrapper+label:after,input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix~label,.input-field.col .prefix~.validate~label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#000;position:absolute;top:0;left:0;font-size:1rem;cursor:text;transition:transform .2s ease-out, color .2s ease-out;transform-origin:0% 100%;text-align:initial;transform:translateY(12px)}.input-field>label:not(.label-icon).active{transform:translateY(-14px) scale(0.8);transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{transform:translateY(-14px) scale(0.8);transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix~input,.input-field .prefix~textarea,.input-field .prefix~label,.input-field .prefix~.validate~label,.input-field .prefix~.helper-text,.input-field .prefix~.autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix~label{margin-left:3rem}@media only screen and (max-width : 992px){.input-field .prefix~input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width : 600px){.input-field .prefix~input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default)~.mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default)~.material-icons{color:#444}.input-field input[type=search]+.label-icon{transform:none;left:1rem}.input-field input[type=search]~.mdi-navigation-close,.input-field input[type=search]~.material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;transition:.28s ease;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{transform:scale(1);border:0;border-radius:50%;box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;transform:rotate(40deg);backface-visibility:hidden;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;transform:rotate(90deg);backface-visibility:hidden;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;transform:rotateZ(37deg);transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;transform:rotateZ(37deg);transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled)~.lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus~.lever::before{transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled)~.lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus~.lever::before{transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix~.select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix~label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup~li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;transform-origin:50% 50%;transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;transition:box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;transition:box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;transition:box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;backface-visibility:hidden;transform:translateX(-105%)}.sidenav.right-aligned{right:0;transform:translateX(105%);left:auto;transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width : 992px){.sidenav.sidenav-fixed{transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{transform:rotate(130deg)}50%{transform:rotate(-5deg)}to{transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{transform:rotate(-130deg)}50%{transform:rotate(5deg)}to{transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;perspective:500px;transform-style:preserve-3d;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:#616161;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:rgba(0,0,0,0.87)}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;transition:visibility 0s}.tap-target-wrapper.open .tap-target{transform:scale(1);opacity:.95;transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;transition:opacity .3s, transform .3s, visibility 0s 1s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;transform:scale(0);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{transform:scale(0);transition:transform .3s}.tap-target-wave::after{visibility:hidden;transition:opacity .3s, transform .3s, visibility 0s;z-index:-1}.tap-target-origin{top:50%;left:50%;transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;transition:opacity .3s, transform .3s;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@keyframes pulse-animation{0%{opacity:1;transform:scale(1)}50%{opacity:0;transform:scale(1.5)}100%{opacity:0;transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:flex;flex-direction:column;padding:0}.datepicker-controls{display:flex;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:flex;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width : 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{flex-direction:row}.datepicker-date-display{flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:flex;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{transition:transform 350ms, opacity 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{transform:scale(0.8, 0.8)}.timepicker-canvas{transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:flex;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width : 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}}.caution-color{background-color:#F44336}.caution-color-text{color:#F44336}.info-color{background-color:#fff9c4}.info-color-text{color:#fff9c4}.contrast-color{background-color:rgba(0,0,0,0.87)}.contrast-color-text{color:rgba(0,0,0,0.87)}.success-color{background-color:#4CAF50}.success-color-text{color:#4CAF50}html{background-color:#fff}a.unmarked{color:rgba(0,0,0,0.87)}.white-text a{color:#eeeeff}div.targetlist{display:grid;grid-template-columns:1fr max-content;align-items:center}div.targetlist>a.nonflex{padding-left:1em;padding-top:1em;padding-bottom:1em;display:inline-block}a.tablerow{display:flex;justify-content:space-between;padding-top:1em;padding-bottom:1em;border-bottom:1px solid rgba(0,0,0,0.12)}a.tablerow .material-icons{vertical-align:bottom;margin-bottom:0.2em}a.tablerow span{display:inline-block}.pagination li a{color:rgba(0,0,0,0.87)}.collection-item{color:rgba(0,0,0,0.87)}.collection-item .secondary-content{color:rgba(0,0,0,0.87)}.collection-item.disabled{color:#616161}.action-checkin,.action-checkout,.action-undo,.action-cancelled-from,.action-cancelled-to,.action-share{cursor:pointer}.config a{cursor:pointer}.navbar-fixed{z-index:1001}.brand-logo span{transition:color 1s}.brand-logo:hover .ca,.brand-logo:hover .ce{color:#a8e3fa !important}.brand-logo:hover .cb,.brand-logo:hover .cd{color:#f5c4ce !important}.wagons span{margin-right:0.5ex;color:#808080}.wagons .wagonclass{font-weight:bold;color:inherit}.wagons .wagonnum{margin-right:0;color:inherit}.wagons .checksum:before{content:"-"}h1{font-size:2.92rem;margin:1.9466666667rem 0 1.168rem 0}h2{font-size:2.28rem;margin:1.52rem 0 .912rem 0}h3{font-size:1.64rem;margin:1.0933333333rem 0 .656rem 0}.geolocation i.material-icons{font-size:16px}ul.suggestions li{padding-top:0.5rem;padding-bottom:0.5rem}.collection.departures>li,.collection.history>li{transition:background .3s;display:grid}.collection.departures>li:not(#now,.history-date-change):hover,.collection.departures>li:focus-within,.collection.history>li:not(#now,.history-date-change):hover,.collection.history>li:focus-within{background-color:#eee;outline:2px solid #039be5}.collection.departures li{grid-template-columns:10ch 10ch 1fr;align-items:center}.collection.departures li#now{background-color:#eee;padding:2rem 20px;grid-template-columns:10ch 1fr}.collection.departures li#now strong{font-weight:bold}.collection.departures li.cancelled{background-color:#FFCDD2;font-style:italic}.collection.departures li.cancelled .dep-line{background-color:transparent;border:1px solid;color:rgba(0,0,0,0.87)}.collection.departures li.cancelled .dep-time::after{content:" ⊖";font-style:normal}.departures .dep-time{color:rgba(0,0,0,0.87)}.departures .dep-time:focus{outline:none}.departures .dep-dest{margin-left:0.8rem}.departures .dep-dest .followee-checkin{font-size:0.9rem;display:block}.departures .dep-dest .followee-checkin i.material-icons{vertical-align:middle}.collection.history>li{display:grid;grid-template-columns:10ch 1fr;grid-template-rows:1fr}.collection.history>li a:first-child{align-self:center;text-align:center;display:flex}.collection.history>li.history-date-change{display:block;font-weight:bold}ul.route-history>li{list-style:none;position:relative;display:grid;grid-template-columns:1rem 1fr;gap:0.5rem}ul.route-history>li a{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul.route-history>li strong{font-weight:600}ul.route-history>li i.material-icons[aria-label=nach]{padding-top:0.4rem}ul.route-history>li i.material-icons[aria-label=von]{display:block;transform:rotate(-90deg);height:1rem;margin-top:0.4rem}ul.route-history>li::before{content:'';background:rgba(0,0,0,0.87);position:absolute;width:2px;left:calc( (1rem - 2px) / 2);bottom:0;top:0}ul.route-history>li:first-of-type::before{top:1.3rem}ul.route-history>li:last-of-type::before{bottom:unset;height:0.5rem}.dep-line{text-align:center;padding:.2rem;color:white;background:#424242;border-radius:.2rem;display:inline-block;font-weight:600;line-height:1;height:fit-content;width:fit-content;min-width:6ch;margin:0 auto}.dep-line.Bus,.dep-line.RUF,.dep-line.AST{background-color:#a3167e;border-radius:5rem;padding:.2rem .5rem}.dep-line.STR,.dep-line.Tram,.dep-line.Str,.dep-line.Strb,.dep-line.STB{background-color:#c5161c;border-radius:5rem;padding:.2rem .5rem}.dep-line.S,.dep-line.RS,.dep-line.RER,.dep-line.SKW{background-color:#008d4f;border-radius:5rem;padding:.2rem .5rem}.dep-line.U,.dep-line.M{background-color:#014e8d;border-radius:5rem;padding:.2rem .5rem}.dep-line.RE,.dep-line.IRE,.dep-line.REX{background-color:#ff4f00}.dep-line.RB,.dep-line.MEX,.dep-line.TER,.dep-line.R{background-color:#1f4a87}.dep-line.IC,.dep-line.ICE,.dep-line.EC,.dep-line.ECE,.dep-line.D,.dep-line.IR,.dep-line.TGV,.dep-line.OGV,.dep-line.EST,.dep-line.TLK,.dep-line.EIC{background-color:#ff0404;font-weight:900;font-style:italic;padding:.2rem}.dep-line.RJ,.dep-line.RJX{background-color:#c63131}.dep-line.NJ,.dep-line.EN{background-color:#29255b}.dep-line.WB{background-color:#2e85ce}.dep-line.FLX{background-color:#71d800;color:black}.departures.connections li{grid-template-columns:15ch 10ch 1fr}.departures.connections .connect-platform-wrapper{text-align:center}.departures.connections .connect-platform-wrapper span{display:block}@media screen and (max-width: 600px){.collection.departures li{grid-template-columns:10ch 1fr}.collection.departures li .dep-line,.collection.departures li .dep-time,.collection.departures li .connect-platform-wrapper{grid-column:1;text-align:center}.collection.departures li .dep-dest{grid-column:2;grid-row:1 / span 2}.departures.connections li{grid-template-columns:15ch 1fr}.departures.connections li .connect-platform-wrapper span{display:inline-block}}a.timeline-link{padding-top:1ex;padding-bottom:1ex}.progress{background-color:#e0e0e0}.progress>.determinate{background-color:#2196F3} + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);transition:all 0.7s ease-out;transition-property:transform, opacity;transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{transition:none !important}.waves-circle{transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width : 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;box-shadow:none}.collapsible.popout>li{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix~.chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty~label{font-size:0.8rem;transform:translateY(-140%)}.materialboxed{display:block;cursor:zoom-in;position:relative;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#000}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;box-shadow:none;box-sizing:content-box;transition:box-shadow .3s, border .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid~label,input[type=text]:not(.browser-default):focus.valid~label,input[type=password]:not(.browser-default):focus.valid~label,input[type=email]:not(.browser-default):focus.valid~label,input[type=url]:not(.browser-default):focus.valid~label,input[type=time]:not(.browser-default):focus.valid~label,input[type=date]:not(.browser-default):focus.valid~label,input[type=datetime]:not(.browser-default):focus.valid~label,input[type=datetime-local]:not(.browser-default):focus.valid~label,input[type=tel]:not(.browser-default):focus.valid~label,input[type=number]:not(.browser-default):focus.valid~label,input[type=search]:not(.browser-default):focus.valid~label,textarea.materialize-textarea:focus.valid~label{color:#4CAF50}input:not([type]):focus.invalid~label,input[type=text]:not(.browser-default):focus.invalid~label,input[type=password]:not(.browser-default):focus.invalid~label,input[type=email]:not(.browser-default):focus.invalid~label,input[type=url]:not(.browser-default):focus.invalid~label,input[type=time]:not(.browser-default):focus.invalid~label,input[type=date]:not(.browser-default):focus.invalid~label,input[type=datetime]:not(.browser-default):focus.invalid~label,input[type=datetime-local]:not(.browser-default):focus.invalid~label,input[type=tel]:not(.browser-default):focus.invalid~label,input[type=number]:not(.browser-default):focus.invalid~label,input[type=search]:not(.browser-default):focus.invalid~label,textarea.materialize-textarea:focus.invalid~label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}.select-wrapper.valid>input.select-dropdown,input.valid:not([type]),input.valid:not([type]):focus,input[type=text].valid:not(.browser-default),input[type=text].valid:not(.browser-default):focus,input[type=password].valid:not(.browser-default),input[type=password].valid:not(.browser-default):focus,input[type=email].valid:not(.browser-default),input[type=email].valid:not(.browser-default):focus,input[type=url].valid:not(.browser-default),input[type=url].valid:not(.browser-default):focus,input[type=time].valid:not(.browser-default),input[type=time].valid:not(.browser-default):focus,input[type=date].valid:not(.browser-default),input[type=date].valid:not(.browser-default):focus,input[type=datetime].valid:not(.browser-default),input[type=datetime].valid:not(.browser-default):focus,input[type=datetime-local].valid:not(.browser-default),input[type=datetime-local].valid:not(.browser-default):focus,input[type=tel].valid:not(.browser-default),input[type=tel].valid:not(.browser-default):focus,input[type=number].valid:not(.browser-default),input[type=number].valid:not(.browser-default):focus,input[type=search].valid:not(.browser-default),input[type=search].valid:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus{border-bottom:1px solid #4CAF50;box-shadow:0 1px 0 0 #4CAF50}.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus,input.invalid:not([type]),input.invalid:not([type]):focus,input[type=text].invalid:not(.browser-default),input[type=text].invalid:not(.browser-default):focus,input[type=password].invalid:not(.browser-default),input[type=password].invalid:not(.browser-default):focus,input[type=email].invalid:not(.browser-default),input[type=email].invalid:not(.browser-default):focus,input[type=url].invalid:not(.browser-default),input[type=url].invalid:not(.browser-default):focus,input[type=time].invalid:not(.browser-default),input[type=time].invalid:not(.browser-default):focus,input[type=date].invalid:not(.browser-default),input[type=date].invalid:not(.browser-default):focus,input[type=datetime].invalid:not(.browser-default),input[type=datetime].invalid:not(.browser-default):focus,input[type=datetime-local].invalid:not(.browser-default),input[type=datetime-local].invalid:not(.browser-default):focus,input[type=tel].invalid:not(.browser-default),input[type=tel].invalid:not(.browser-default):focus,input[type=number].invalid:not(.browser-default),input[type=number].invalid:not(.browser-default):focus,input[type=search].invalid:not(.browser-default),input[type=search].invalid:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus{border-bottom:1px solid #F44336;box-shadow:0 1px 0 0 #F44336}.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid~.helper-text[data-error],input.valid:not([type])~.helper-text[data-success],input.valid:not([type]):focus~.helper-text[data-success],input.invalid:not([type])~.helper-text[data-error],input.invalid:not([type]):focus~.helper-text[data-error],input[type=text].valid:not(.browser-default)~.helper-text[data-success],input[type=text].invalid:not(.browser-default)~.helper-text[data-error],input[type=password].valid:not(.browser-default)~.helper-text[data-success],input[type=password].invalid:not(.browser-default)~.helper-text[data-error],input[type=email].valid:not(.browser-default)~.helper-text[data-success],input[type=email].invalid:not(.browser-default)~.helper-text[data-error],input[type=url].valid:not(.browser-default)~.helper-text[data-success],input[type=url].invalid:not(.browser-default)~.helper-text[data-error],input[type=time].valid:not(.browser-default)~.helper-text[data-success],input[type=time].invalid:not(.browser-default)~.helper-text[data-error],input[type=date].valid:not(.browser-default)~.helper-text[data-success],input[type=date].invalid:not(.browser-default)~.helper-text[data-error],input[type=datetime].valid:not(.browser-default)~.helper-text[data-success],input[type=datetime].invalid:not(.browser-default)~.helper-text[data-error],input[type=datetime-local].valid:not(.browser-default)~.helper-text[data-success],input[type=datetime-local].invalid:not(.browser-default)~.helper-text[data-error],input[type=tel].valid:not(.browser-default)~.helper-text[data-success],input[type=tel].invalid:not(.browser-default)~.helper-text[data-error],input[type=number].valid:not(.browser-default)~.helper-text[data-success],input[type=number].invalid:not(.browser-default)~.helper-text[data-error],input[type=search].valid:not(.browser-default)~.helper-text[data-success],input[type=search].invalid:not(.browser-default)~.helper-text[data-error],textarea.materialize-textarea.valid~.helper-text[data-success],textarea.materialize-textarea.valid:focus~.helper-text[data-success],textarea.materialize-textarea.invalid~.helper-text[data-error],textarea.materialize-textarea.invalid:focus~.helper-text[data-error]{color:transparent;user-select:none;pointer-events:none}.select-wrapper.valid~.helper-text:after,input.valid:not([type])~.helper-text:after,input.valid:not([type]):focus~.helper-text:after,input[type=text].valid:not(.browser-default)~.helper-text:after,input[type=text].valid:not(.browser-default):focus~.helper-text:after,input[type=password].valid:not(.browser-default)~.helper-text:after,input[type=password].valid:not(.browser-default):focus~.helper-text:after,input[type=email].valid:not(.browser-default)~.helper-text:after,input[type=email].valid:not(.browser-default):focus~.helper-text:after,input[type=url].valid:not(.browser-default)~.helper-text:after,input[type=url].valid:not(.browser-default):focus~.helper-text:after,input[type=time].valid:not(.browser-default)~.helper-text:after,input[type=time].valid:not(.browser-default):focus~.helper-text:after,input[type=date].valid:not(.browser-default)~.helper-text:after,input[type=date].valid:not(.browser-default):focus~.helper-text:after,input[type=datetime].valid:not(.browser-default)~.helper-text:after,input[type=datetime].valid:not(.browser-default):focus~.helper-text:after,input[type=datetime-local].valid:not(.browser-default)~.helper-text:after,input[type=datetime-local].valid:not(.browser-default):focus~.helper-text:after,input[type=tel].valid:not(.browser-default)~.helper-text:after,input[type=tel].valid:not(.browser-default):focus~.helper-text:after,input[type=number].valid:not(.browser-default)~.helper-text:after,input[type=number].valid:not(.browser-default):focus~.helper-text:after,input[type=search].valid:not(.browser-default)~.helper-text:after,input[type=search].valid:not(.browser-default):focus~.helper-text:after,textarea.materialize-textarea.valid~.helper-text:after,textarea.materialize-textarea.valid:focus~.helper-text:after{content:attr(data-success);color:#4CAF50}.select-wrapper.invalid~.helper-text:after,input.invalid:not([type])~.helper-text:after,input.invalid:not([type]):focus~.helper-text:after,input[type=text].invalid:not(.browser-default)~.helper-text:after,input[type=text].invalid:not(.browser-default):focus~.helper-text:after,input[type=password].invalid:not(.browser-default)~.helper-text:after,input[type=password].invalid:not(.browser-default):focus~.helper-text:after,input[type=email].invalid:not(.browser-default)~.helper-text:after,input[type=email].invalid:not(.browser-default):focus~.helper-text:after,input[type=url].invalid:not(.browser-default)~.helper-text:after,input[type=url].invalid:not(.browser-default):focus~.helper-text:after,input[type=time].invalid:not(.browser-default)~.helper-text:after,input[type=time].invalid:not(.browser-default):focus~.helper-text:after,input[type=date].invalid:not(.browser-default)~.helper-text:after,input[type=date].invalid:not(.browser-default):focus~.helper-text:after,input[type=datetime].invalid:not(.browser-default)~.helper-text:after,input[type=datetime].invalid:not(.browser-default):focus~.helper-text:after,input[type=datetime-local].invalid:not(.browser-default)~.helper-text:after,input[type=datetime-local].invalid:not(.browser-default):focus~.helper-text:after,input[type=tel].invalid:not(.browser-default)~.helper-text:after,input[type=tel].invalid:not(.browser-default):focus~.helper-text:after,input[type=number].invalid:not(.browser-default)~.helper-text:after,input[type=number].invalid:not(.browser-default):focus~.helper-text:after,input[type=search].invalid:not(.browser-default)~.helper-text:after,input[type=search].invalid:not(.browser-default):focus~.helper-text:after,textarea.materialize-textarea.invalid~.helper-text:after,textarea.materialize-textarea.invalid:focus~.helper-text:after{content:attr(data-error);color:#F44336}.select-wrapper+label:after,input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix~label,.input-field.col .prefix~.validate~label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#000;position:absolute;top:0;left:0;font-size:1rem;cursor:text;transition:transform .2s ease-out, color .2s ease-out;transform-origin:0% 100%;text-align:initial;transform:translateY(12px)}.input-field>label:not(.label-icon).active{transform:translateY(-14px) scale(0.8);transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{transform:translateY(-14px) scale(0.8);transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix~input,.input-field .prefix~textarea,.input-field .prefix~label,.input-field .prefix~.validate~label,.input-field .prefix~.helper-text,.input-field .prefix~.autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix~label{margin-left:3rem}@media only screen and (max-width : 992px){.input-field .prefix~input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width : 600px){.input-field .prefix~input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default)~.mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default)~.material-icons{color:#444}.input-field input[type=search]+.label-icon{transform:none;left:1rem}.input-field input[type=search]~.mdi-navigation-close,.input-field input[type=search]~.material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;transition:.28s ease;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{transform:scale(1);border:0;border-radius:50%;box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;transform:rotate(40deg);backface-visibility:hidden;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;transform:rotate(90deg);backface-visibility:hidden;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;transform:rotateZ(37deg);transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;transform:rotateZ(37deg);transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled)~.lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus~.lever::before{transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled)~.lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus~.lever::before{transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix~.select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix~label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup~li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;transform-origin:50% 50%;transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;transition:box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;transition:box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;transition:box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;backface-visibility:hidden;transform:translateX(-105%)}.sidenav.right-aligned{right:0;transform:translateX(105%);left:auto;transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width : 992px){.sidenav.sidenav-fixed{transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}to{transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{transform:rotate(130deg)}50%{transform:rotate(-5deg)}to{transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{transform:rotate(-130deg)}50%{transform:rotate(5deg)}to{transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;perspective:500px;transform-style:preserve-3d;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:#616161;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:rgba(0,0,0,0.87)}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;transition:visibility 0s}.tap-target-wrapper.open .tap-target{transform:scale(1);opacity:.95;transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;transition:opacity .3s, transform .3s, visibility 0s 1s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;transform:scale(0);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{transform:scale(0);transition:transform .3s}.tap-target-wave::after{visibility:hidden;transition:opacity .3s, transform .3s, visibility 0s;z-index:-1}.tap-target-origin{top:50%;left:50%;transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;transition:opacity .3s, transform .3s;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@keyframes pulse-animation{0%{opacity:1;transform:scale(1)}50%{opacity:0;transform:scale(1.5)}100%{opacity:0;transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:flex;flex-direction:column;padding:0}.datepicker-controls{display:flex;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:flex;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width : 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{flex-direction:row}.datepicker-date-display{flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:flex;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{transition:transform 350ms, opacity 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{transform:scale(0.8, 0.8)}.timepicker-canvas{transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:flex;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width : 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}}.caution-color{background-color:#F44336}.caution-color-text{color:#F44336}.info-color{background-color:#fff9c4}.info-color-text{color:#fff9c4}.contrast-color{background-color:rgba(0,0,0,0.87)}.contrast-color-text{color:rgba(0,0,0,0.87)}.success-color{background-color:#4CAF50}.success-color-text{color:#4CAF50}html{background-color:#fff}a.unmarked{color:rgba(0,0,0,0.87)}.white-text a{color:#eeeeff}div.targetlist{display:grid;grid-template-columns:1fr max-content;align-items:center}div.targetlist>a.nonflex{padding-left:1em;padding-top:1em;padding-bottom:1em;display:inline-block}a.tablerow{display:flex;justify-content:space-between;padding-top:1em;padding-bottom:1em;border-bottom:1px solid rgba(0,0,0,0.12)}a.tablerow .material-icons{vertical-align:bottom;margin-bottom:0.2em}a.tablerow span{display:inline-block}.pagination li a{color:rgba(0,0,0,0.87)}.collection-item{color:rgba(0,0,0,0.87)}.collection-item .secondary-content{color:rgba(0,0,0,0.87)}.collection-item.disabled{color:#616161}.action-checkin,.action-checkout,.action-undo,.action-cancelled-from,.action-cancelled-to,.action-share{cursor:pointer}.config a{cursor:pointer}.navbar-fixed{z-index:1001}.brand-logo span{transition:color 1s}.brand-logo:hover .ca,.brand-logo:hover .ce{color:#a8e3fa !important}.brand-logo:hover .cb,.brand-logo:hover .cd{color:#f5c4ce !important}.wagons span{margin-right:0.5ex;color:#808080}.wagons .wagonclass{font-weight:bold;color:inherit}.wagons .wagonnum{margin-right:0;color:inherit}.wagons .checksum:before{content:"-"}h1{font-size:2.92rem;margin:1.9466666667rem 0 1.168rem 0}h2{font-size:2.28rem;margin:1.52rem 0 .912rem 0}h3{font-size:1.64rem;margin:1.0933333333rem 0 .656rem 0}.geolocation i.material-icons{font-size:16px}ul.suggestions li{padding-top:0.5rem;padding-bottom:0.5rem}.collection.departures>li,.collection.history>li{transition:background .3s;display:grid}.collection.departures>li:not(#now,.history-date-change):hover,.collection.departures>li:focus-within,.collection.history>li:not(#now,.history-date-change):hover,.collection.history>li:focus-within{background-color:#eee;outline:2px solid #039be5}.collection.departures li{grid-template-columns:10ch 10ch 1fr;align-items:center}.collection.departures li#now{background-color:#eee;padding:2rem 20px;grid-template-columns:10ch 1fr}.collection.departures li#now strong{font-weight:bold}.collection.departures li.cancelled{background-color:#FFCDD2;font-style:italic}.collection.departures li.cancelled .dep-line{background-color:transparent;border:1px solid;color:rgba(0,0,0,0.87)}.collection.departures li.cancelled .dep-time::after{content:" ⊖";font-style:normal}.departures .dep-time{color:rgba(0,0,0,0.87)}.departures .dep-time:focus{outline:none}.departures .dep-dest{margin-left:0.8rem}.departures .dep-dest .followee-checkin{font-size:0.9rem;display:block}.departures .dep-dest .followee-checkin i.material-icons{vertical-align:middle}.collection.history>li{display:grid;grid-template-columns:10ch 1fr;grid-template-rows:1fr}.collection.history>li a:first-child{align-self:center;text-align:center;display:flex}.collection.history>li.history-date-change{display:block;font-weight:bold}ul.route-history>li{list-style:none;position:relative;display:grid;grid-template-columns:1rem 1fr;gap:0.5rem}ul.route-history>li a{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul.route-history>li strong{font-weight:600}ul.route-history>li i.material-icons[aria-label=nach]{padding-top:0.4rem}ul.route-history>li i.material-icons[aria-label=von]{display:block;transform:rotate(-90deg);height:1rem;margin-top:0.4rem}ul.route-history>li::before{content:'';background:rgba(0,0,0,0.87);position:absolute;width:2px;left:calc( (1rem - 2px) / 2);bottom:0;top:0}ul.route-history>li:first-of-type::before{top:1.3rem}ul.route-history>li:last-of-type::before{bottom:unset;height:0.5rem}.dep-line{text-align:center;padding:.2rem;color:white;background:#424242;border-radius:.2rem;display:inline-block;font-weight:600;line-height:1;height:fit-content;width:fit-content;min-width:6ch;margin:0 auto}.dep-line.Bus,.dep-line.BUS,.dep-line.RUF,.dep-line.AST{background-color:#a3167e;border-radius:5rem;padding:.2rem .5rem}.dep-line.STR,.dep-line.Tram,.dep-line.TRAM,.dep-line.Str,.dep-line.Strb,.dep-line.STB{background-color:#c5161c;border-radius:5rem;padding:.2rem .5rem}.dep-line.S,.dep-line.RS,.dep-line.RER,.dep-line.SKW,.dep-line.METRO{background-color:#008d4f;border-radius:5rem;padding:.2rem .5rem}.dep-line.U,.dep-line.M,.dep-line.SUBWAY{background-color:#014e8d;border-radius:5rem;padding:.2rem .5rem}.dep-line.RE,.dep-line.IRE,.dep-line.REX,.dep-line.REGIONAL_FAST_RAIL{background-color:#ff4f00}.dep-line.RB,.dep-line.MEX,.dep-line.TER,.dep-line.R,.dep-line.REGIONAL_RAIL{background-color:#1f4a87}.dep-line.IC,.dep-line.ICE,.dep-line.EC,.dep-line.ECE,.dep-line.D,.dep-line.IR,.dep-line.TGV,.dep-line.OGV,.dep-line.EST,.dep-line.TLK,.dep-line.EIC,.dep-line.HIGHSPEED_RAIL,.dep-line.LONG_DISTANCE{background-color:#ff0404;font-weight:900;font-style:italic;padding:.2rem}.dep-line.RJ,.dep-line.RJX{background-color:#c63131}.dep-line.NJ,.dep-line.EN,.dep-line.NIGHT_RAIL{background-color:#29255b}.dep-line.WB{background-color:#2e85ce}.dep-line.FLX{background-color:#71d800;color:black}.departures.connections li{grid-template-columns:15ch 10ch 1fr}.departures.connections .connect-platform-wrapper{text-align:center}.departures.connections .connect-platform-wrapper span{display:block}.status-card-progress-annot{padding-bottom:2ex;border-bottom:2px dashed #808080}.timeline-in-transit .status-card-progress-annot{border-bottom:none}@media screen and (max-width: 600px){.collection.departures li{grid-template-columns:10ch 1fr}.collection.departures li .dep-line,.collection.departures li .dep-time,.collection.departures li .connect-platform-wrapper{grid-column:1;text-align:center}.collection.departures li .dep-dest{grid-column:2;grid-row:1 / span 2}.departures.connections li{grid-template-columns:15ch 1fr}.departures.connections li .connect-platform-wrapper span{display:inline-block}}a.timeline-link{padding-top:1ex;padding-bottom:1ex}.progress{background-color:#e0e0e0}.progress>.determinate{background-color:#2196F3} diff --git a/public/static/css/material-icons.css b/public/static/css/material-icons.css index 422d81e..a5bba8d 100644 --- a/public/static/css/material-icons.css +++ b/public/static/css/material-icons.css @@ -2,12 +2,12 @@ font-family: 'Material Icons'; font-style: normal; font-weight: 400; - src: url(/static/v82/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */ + src: url(/static/v87/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */ src: local('Material Icons'), local('MaterialIcons-Regular'), - url(/static/v82/fonts/MaterialIcons-Regular.woff2) format('woff2'), - url(/static/v82/fonts/MaterialIcons-Regular.woff) format('woff'), - url(/static/v82/fonts/MaterialIcons-Regular.ttf) format('truetype'); + url(/static/v87/fonts/MaterialIcons-Regular.woff2) format('woff2'), + url(/static/v87/fonts/MaterialIcons-Regular.woff) format('woff'), + url(/static/v87/fonts/MaterialIcons-Regular.ttf) format('truetype'); } .material-icons { diff --git a/public/static/js/geolocation.js b/public/static/js/geolocation.js index a496900..1bb4b2b 100644 --- a/public/static/js/geolocation.js +++ b/public/static/js/geolocation.js @@ -24,7 +24,9 @@ $(document).ready(function() { const res = $(document.createElement('p')); $.each(stops, function(i, stop) { const parts = stop.split(';'); - const node = $('<a class="tablerow" href="/s/' + parts[0] + '?hafas=' + parts[2] + '"><span><i class="material-icons" aria-hidden="true">' + (parts[2] == '0' ? 'train' : 'directions') + '</i>' + parts[1] + '</span></a>'); + const [ eva, name, dbris, motis, hafas ] = parts; + + const node = $('<a class="tablerow" href="/s/' + eva + '?dbris=' + dbris + '&motis=' + motis + '&hafas=' + hafas + '"><span><i class="material-icons" aria-hidden="true">' + (dbris == '0' ? 'train' : 'directions') + '</i>' + name + '</span></a>'); node.click(function() { $('nav .preloader-wrapper').addClass('active'); }); @@ -45,13 +47,21 @@ $(document).ready(function() { } else { const res = $(document.createElement('p')); $.each(data.candidates, function(i, candidate) { + let node; + + if (candidate.motis !== undefined) { + const { id, name, motis } = candidate; + + node = $('<a class="tablerow" href="/s/' + id + '?motis=' + motis + '"><span><i class="material-icons" aria-hidden="true">train</i>' + name + '</span></a>'); + } else { + const eva = candidate.eva, + name = candidate.name, + hafas = candidate.hafas, + distance = candidate.distance.toFixed(1); - const eva = candidate.eva, - name = candidate.name, - hafas = candidate.hafas, - distance = candidate.distance.toFixed(1); + node = $('<a class="tablerow" href="/s/' + eva + '?hafas=' + hafas + '"><span><i class="material-icons" aria-hidden="true">' + (hafas == '0' ? 'train' : 'directions') + '</i>' + name + '</span></a>'); + } - const node = $('<a class="tablerow" href="/s/' + eva + '?hafas=' + hafas + '"><span><i class="material-icons" aria-hidden="true">' + (hafas == '0' ? 'train' : 'directions') + '</i>' + name + '</span></a>'); node.click(function() { $('nav .preloader-wrapper').addClass('active'); }); diff --git a/public/static/js/geolocation.min.js b/public/static/js/geolocation.min.js index 51f91fa..4dd53a3 100644 --- a/public/static/js/geolocation.min.js +++ b/public/static/js/geolocation.min.js @@ -1 +1 @@ -$(document).ready(function(){function r(){return $("div.geolocation div.progress")}function e(e){var n=$("div.geolocation").data("backend");$.post("/geolocation",{lon:e.coords.longitude,lat:e.coords.latitude,backend:n},o)}function n(e){e.code==e.PERMISSION_DENIED?t("Standortanfrage nicht möglich.","Vermutlich fehlen die Rechte im Browser oder der Android Location Service ist deaktiviert.","geolocation.error.PERMISSION_DENIED"):e.code==e.POSITION_UNAVAILABLE?t("Standort konnte nicht ermittelt werden","(Service nicht verfügbar)","geolocation.error.POSITION_UNAVAILABLE"):e.code==e.TIMEOUT?t("Standort konnte nicht ermittelt werden","(Timeout)","geolocation.error.TIMEOUT"):t("Standort konnte nicht ermittelt werden","(unbekannter Fehler)","unknown geolocation.error code")}const t=function(e,n,t){var o=$(document.createElement("div")),n=(o.attr("class","error"),o.text(n),$(document.createElement("strong"))),e=(n.text(e+" "),o.prepend(n),$("div.geolocation").append(o),$("div.geolocation").data("recent"));if(e){n=e.split("|");const a=$(document.createElement("p"));$.each(n,function(e,n){n=n.split(";"),n=$('<a class="tablerow" href="/s/'+n[0]+"?hafas="+n[2]+'"><span><i class="material-icons" aria-hidden="true">'+("0"==n[2]?"train":"directions")+"</i>"+n[1]+"</span></a>");n.click(function(){$("nav .preloader-wrapper").addClass("active")}),a.append(n)}),$("p.geolocationhint").text("Letzte Ziele:"),r().replaceWith(a)}else r().remove()},o=function(e){if(e.error)t("Backend-Fehler:",e.error,null);else if(0==e.candidates.length)t("Keine Bahnhöfe in 70km Umkreis gefunden","",null);else{const i=$(document.createElement("p"));$.each(e.candidates,function(e,n){var t=n.eva,o=n.name,a=n.hafas,n=(n.distance.toFixed(1),$('<a class="tablerow" href="/s/'+t+"?hafas="+a+'"><span><i class="material-icons" aria-hidden="true">'+("0"==a?"train":"directions")+"</i>"+o+"</span></a>"));n.click(function(){$("nav .preloader-wrapper").addClass("active")}),i.append(n)}),r().replaceWith(i)}},a=$("div.geolocation > .request");a.data("recent");function i(){a.replaceWith($('<p class="geolocationhint">Stationen in der Umgebung:</p><div class="progress"><div class="indeterminate"></div></div>')),navigator.geolocation.getCurrentPosition(e,n)}a.length&&(navigator.geolocation?navigator.permissions?navigator.permissions.query({name:"geolocation"}).then(function(e){"prompt"===e.state?a.on("click",i):i()}):a.on("click",i):t("Standortanfragen werden von diesem Browser nicht unterstützt","",null))}); +$(document).ready(function(){function i(){return $("div.geolocation div.progress")}function e(e){var t=$("div.geolocation").data("backend");$.post("/geolocation",{lon:e.coords.longitude,lat:e.coords.latitude,backend:t},a)}function t(e){e.code==e.PERMISSION_DENIED?n("Standortanfrage nicht möglich.","Vermutlich fehlen die Rechte im Browser oder der Android Location Service ist deaktiviert.","geolocation.error.PERMISSION_DENIED"):e.code==e.POSITION_UNAVAILABLE?n("Standort konnte nicht ermittelt werden","(Service nicht verfügbar)","geolocation.error.POSITION_UNAVAILABLE"):e.code==e.TIMEOUT?n("Standort konnte nicht ermittelt werden","(Timeout)","geolocation.error.TIMEOUT"):n("Standort konnte nicht ermittelt werden","(unbekannter Fehler)","unknown geolocation.error code")}const n=function(e,t,n){var a=$(document.createElement("div")),t=(a.attr("class","error"),a.text(t),$(document.createElement("strong"))),e=(t.text(e+" "),a.prepend(t),$("div.geolocation").append(a),$("div.geolocation").data("recent"));if(e){t=e.split("|");const r=$(document.createElement("p"));$.each(t,function(e,t){var[t,n,a,i,o]=t.split(";"),t=$('<a class="tablerow" href="/s/'+t+"?dbris="+a+"&motis="+i+"&hafas="+o+'"><span><i class="material-icons" aria-hidden="true">'+("0"==a?"train":"directions")+"</i>"+n+"</span></a>");t.click(function(){$("nav .preloader-wrapper").addClass("active")}),r.append(t)}),$("p.geolocationhint").text("Letzte Ziele:"),i().replaceWith(r)}else i().remove()},a=function(e){if(e.error)n("Backend-Fehler:",e.error,null);else if(0==e.candidates.length)n("Keine Bahnhöfe in 70km Umkreis gefunden","",null);else{const r=$(document.createElement("p"));$.each(e.candidates,function(e,t){let n;var a,i,o;(n=void 0!==t.motis?({id:a,name:o,motis:i}=t,$('<a class="tablerow" href="/s/'+a+"?motis="+i+'"><span><i class="material-icons" aria-hidden="true">train</i>'+o+"</span></a>")):(a=t.eva,i=t.name,o=t.hafas,t.distance.toFixed(1),$('<a class="tablerow" href="/s/'+a+"?hafas="+o+'"><span><i class="material-icons" aria-hidden="true">'+("0"==o?"train":"directions")+"</i>"+i+"</span></a>"))).click(function(){$("nav .preloader-wrapper").addClass("active")}),r.append(n)}),i().replaceWith(r)}},o=$("div.geolocation > .request");o.data("recent");function r(){o.replaceWith($('<p class="geolocationhint">Stationen in der Umgebung:</p><div class="progress"><div class="indeterminate"></div></div>')),navigator.geolocation.getCurrentPosition(e,t)}o.length&&(navigator.geolocation?navigator.permissions?navigator.permissions.query({name:"geolocation"}).then(function(e){"prompt"===e.state?o.on("click",r):r()}):o.on("click",r):n("Standortanfragen werden von diesem Browser nicht unterstützt","",null))}); diff --git a/public/static/js/travelynx-actions.js b/public/static/js/travelynx-actions.js index d4ddf45..d2316af 100644 --- a/public/static/js/travelynx-actions.js +++ b/public/static/js/travelynx-actions.js @@ -194,9 +194,12 @@ function tvly_reg_handlers() { var link = $(this); var req = { action: 'checkin', + dbris: link.data('dbris'), hafas: link.data('hafas'), + motis: link.data('motis'), station: link.data('station'), train: link.data('train'), + suffix: link.data('suffix'), dest: link.data('dest'), ts: link.data('ts'), }; @@ -206,7 +209,9 @@ function tvly_reg_handlers() { var link = $(this); var req = { action: 'checkout', + dbris: link.data('dbris'), hafas: link.data('hafas'), + motis: link.data('motis'), station: link.data('station'), force: link.data('force'), }; @@ -237,7 +242,9 @@ function tvly_reg_handlers() { var link = $(this); var req = { action: 'cancelled_from', + dbris: link.data('dbris'), hafas: link.data('hafas'), + motis: link.data('motis'), station: link.data('station'), ts: link.data('ts'), train: link.data('train'), @@ -248,7 +255,9 @@ function tvly_reg_handlers() { var link = $(this); var req = { action: 'cancelled_to', + dbris: link.data('dbris'), hafas: link.data('hafas'), + motis: link.data('motis'), station: link.data('station'), force: true, }; @@ -315,18 +324,18 @@ $(document).ready(function() { $('nav .preloader-wrapper').addClass('active'); }); $('a[href="#now"]').keydown(function(event) { - // also trigger click handler on keyboard enter - if (event.keyCode == 13) { - event.preventDefault(); - event.target.click(); - } + // also trigger click handler on keyboard enter + if (event.keyCode == 13) { + event.preventDefault(); + event.target.click(); + } }); $('a[href="#now"]').click(function(event) { - event.preventDefault(); - $('nav .preloader-wrapper').removeClass('active'); - now_el = $('#now')[0]; - now_el.previousElementSibling.querySelector(".dep-time").focus(); - now_el.scrollIntoView({behavior: "smooth", block: "center"}); + event.preventDefault(); + $('nav .preloader-wrapper').removeClass('active'); + now_el = $('#now')[0]; + now_el.previousElementSibling.querySelector(".dep-time").focus(); + now_el.scrollIntoView({behavior: "smooth", block: "center"}); }); const elems = document.querySelectorAll('.carousel'); const instances = M.Carousel.init(elems, { diff --git a/public/static/js/travelynx-actions.min.js b/public/static/js/travelynx-actions.min.js index ff386bd..4d7f368 100644 --- a/public/static/js/travelynx-actions.min.js +++ b/public/static/js/travelynx-actions.min.js @@ -1 +1 @@ -var j_departure=0,j_duration=0,j_arrival=0,j_dest="",j_stops=[],j_token="";function setTheme(t){localStorage.setItem("theme",t),otherTheme.hasOwnProperty(t)||(t=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),addStyleSheet(t,"theme")}function upd_journey_data(){$(".countdown").each(function(){var t=$(this).data("token"),t=(t&&(j_token=t),$(this).data("journey")),t=(t&&(t=t.split(";"),j_departure=parseInt(t[0]),j_arrival=parseInt(t[1]),j_duration=j_arrival-j_departure),$(this).data("dest")),e=(t&&(j_dest=t),$(this).data("route"));if(e)for(var a in e=e.split("|"),j_stops=[],e){for(var n=e[a].split(";"),o=1;o<5;o++)n[o]=parseInt(n[o]);j_stops.push(n)}})}function upd_countdown(){var t=Date.now()/1e3;t<j_departure?$(".countdown").text("Abfahrt in "+Math.round((j_departure-t)/60)+" Minuten"):0<j_arrival&&(t<j_arrival?120<=(t=Math.round((j_arrival-t)/60))?$(".countdown").text("Ankunft in "+Math.floor(t/60)+" Stunden und "+t%60+" Minuten"):60<=t?$(".countdown").text("Ankunft in 1 Stunde und "+t%60+" Minuten"):$(".countdown").text("Ankunft in "+t+" Minuten"):$(".countdown").text("Ziel erreicht"))}function hhmm(t){var t=new Date(1e3*t),e=t.getHours(),t=t.getMinutes();return(e<10?"0"+e:e)+":"+(t<10?"0"+t:t)}function odelay(t,e){return 0==t?"":t<e?" (+"+(e-t)/60+")":t==e?"":" ("+(e-t)/60+")"}function tvly_run(e,t,a){var n='<i class="material-icons">error</i>',o=e.data("tr")?$('<tr><td colspan="'+e.data("tr")+'"><div class="progress"><div class="indeterminate"></div></div></td></tr>'):$('<div class="progress"><div class="indeterminate"></div></div>');e.hide(),e.after(o),$.post("/action",t,function(t){t.success?$(location).attr("href",t.redirect_to):(M.toast({html:n+" "+t.error}),o.remove(),a&&a(),e.append(" "+n),e.show())})}function tvly_update(){$.get("/ajax/status_card.html",function(t){$(".statuscol").html(t),tvly_reg_handlers(),upd_journey_data(),setTimeout(tvly_update,4e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),upd_countdown(),setTimeout(tvly_update,5e3)})}function tvly_update_public(){var t,e=0;$(".publicstatuscol").each(function(){t=$(this).data("user"),e=$(this).data("profile")}),$.get("/ajax/status/"+t+".html",{token:j_token,profile:e},function(t){$(".publicstatuscol").html(t),upd_journey_data(),setTimeout(tvly_update_public,4e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),upd_countdown(),setTimeout(tvly_update_public,5e3)})}function tvly_update_timeline(){$.get("/timeline/in-transit",{ajax:1},function(t){$(".timeline-in-transit").html(t),setTimeout(tvly_update_timeline,6e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),setTimeout(tvly_update_timeline,1e4)})}function tvly_journey_progress(){var t=Date.now()/1e3,e=0;if(0<j_duration){for(stop in 1<(e=(e=1-(j_arrival-t)/j_duration)<0?0:e)&&(e=1),$(".progress .determinate").css("width",100*e+"%"),j_stops){var a=j_stops[stop][0],n=j_stops[stop][1],o=j_stops[stop][2],i=j_stops[stop][3],r=j_stops[stop][4];if(a==j_dest){$(".next-stop").html("");break}if(0!=o&&0<o-t){$(".next-stop").html(a+"<br/>"+hhmm(o)+odelay(n,o));break}if(0!=r&&0<r-t){0!=o?$(".next-stop").html(a+"<br/>"+hhmm(o)+" → "+hhmm(r)+odelay(i,r)):$(".next-stop").html(a+"<br/>"+hhmm(r)+odelay(i,r));break}}setTimeout(tvly_journey_progress,5e3)}}function tvly_reg_handlers(){$(".action-checkin").click(function(){var t=$(this),e={action:"checkin",hafas:t.data("hafas"),station:t.data("station"),train:t.data("train"),dest:t.data("dest"),ts:t.data("ts")};tvly_run(t,e)}),$(".action-checkout").click(function(){var t=$(this),e={action:"checkout",hafas:t.data("hafas"),station:t.data("station"),force:t.data("force")};tvly_run(t,e,function(){t.data("force")||(t.append(" – Ohne Echtzeitdaten auschecken?"),t.data("force",!0))})}),$(".action-undo").click(function(){var t=$(this),e=Date.now()/1e3,a=parseInt(t.data("checkints")),n={action:"undo",undo_id:t.data("id")},o=!0;(o=900<e-a?confirm("Checkin wirklich rückgängig machen? Er kann ggf. nicht wiederholt werden."):o)&&tvly_run(t,n)}),$(".action-cancelled-from").click(function(){var t=$(this),e={action:"cancelled_from",hafas:t.data("hafas"),station:t.data("station"),ts:t.data("ts"),train:t.data("train")};tvly_run(t,e)}),$(".action-cancelled-to").click(function(){var t=$(this),e={action:"cancelled_to",hafas:t.data("hafas"),station:t.data("station"),force:!0};tvly_run(t,e)}),$(".action-delete").click(function(){var t=$(this),e={action:"delete",id:t.data("id"),checkin:t.data("checkin"),checkout:t.data("checkout")};confirm("Diese Fahrt wirklich löschen? Der Eintrag wird sofort aus der Datenbank entfernt und kann nicht wiederhergestellt werden.")&&tvly_run(t,e)}),$(".action-share").click(function(){var t=$(this).data("text"),e=$(this).data("url");navigator.share?(shareObj={text:t},e&&(shareObj.url=e),navigator.share(shareObj)):(e&&(t+=" "+e),(e=document.createElement("textarea")).value=t,e.setAttribute("readonly",""),e.style.position="absolute",e.style.left="-9999px",document.body.appendChild(e),e.select(),e.setSelectionRange(0,99999),document.execCommand("copy"),document.body.removeChild(e),M.toast({html:"Text kopiert: „"+t+"“"}))})}$(document).ready(function(){tvly_reg_handlers(),$(".statuscol .autorefresh").length&&(upd_journey_data(),setTimeout(tvly_update,4e4),setTimeout(tvly_journey_progress,5e3)),$(".publicstatuscol .autorefresh").length&&(upd_journey_data(),setTimeout(tvly_update_public,4e4),setTimeout(tvly_journey_progress,5e3)),$(".timeline-in-transit .autorefresh").length&&setTimeout(tvly_update_timeline,6e4),$("a[href]").click(function(){$("nav .preloader-wrapper").addClass("active")}),$('a[href="#now"]').keydown(function(t){13==t.keyCode&&(t.preventDefault(),t.target.click())}),$('a[href="#now"]').click(function(t){t.preventDefault(),$("nav .preloader-wrapper").removeClass("active"),(now_el=$("#now")[0]).previousElementSibling.querySelector(".dep-time").focus(),now_el.scrollIntoView({behavior:"smooth",block:"center"})});var t=document.querySelectorAll(".carousel");M.Carousel.init(t,{fullWidth:!0,indicators:!0})}); +var j_departure=0,j_duration=0,j_arrival=0,j_dest="",j_stops=[],j_token="";function setTheme(t){localStorage.setItem("theme",t),otherTheme.hasOwnProperty(t)||(t=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),addStyleSheet(t,"theme")}function upd_journey_data(){$(".countdown").each(function(){var t=$(this).data("token"),t=(t&&(j_token=t),$(this).data("journey")),t=(t&&(t=t.split(";"),j_departure=parseInt(t[0]),j_arrival=parseInt(t[1]),j_duration=j_arrival-j_departure),$(this).data("dest")),e=(t&&(j_dest=t),$(this).data("route"));if(e)for(var a in e=e.split("|"),j_stops=[],e){for(var n=e[a].split(";"),i=1;i<5;i++)n[i]=parseInt(n[i]);j_stops.push(n)}})}function upd_countdown(){var t=Date.now()/1e3;t<j_departure?$(".countdown").text("Abfahrt in "+Math.round((j_departure-t)/60)+" Minuten"):0<j_arrival&&(t<j_arrival?120<=(t=Math.round((j_arrival-t)/60))?$(".countdown").text("Ankunft in "+Math.floor(t/60)+" Stunden und "+t%60+" Minuten"):60<=t?$(".countdown").text("Ankunft in 1 Stunde und "+t%60+" Minuten"):$(".countdown").text("Ankunft in "+t+" Minuten"):$(".countdown").text("Ziel erreicht"))}function hhmm(t){var t=new Date(1e3*t),e=t.getHours(),t=t.getMinutes();return(e<10?"0"+e:e)+":"+(t<10?"0"+t:t)}function odelay(t,e){return 0==t?"":t<e?" (+"+(e-t)/60+")":t==e?"":" ("+(e-t)/60+")"}function tvly_run(e,t,a){var n='<i class="material-icons">error</i>',i=e.data("tr")?$('<tr><td colspan="'+e.data("tr")+'"><div class="progress"><div class="indeterminate"></div></div></td></tr>'):$('<div class="progress"><div class="indeterminate"></div></div>');e.hide(),e.after(i),$.post("/action",t,function(t){t.success?$(location).attr("href",t.redirect_to):(M.toast({html:n+" "+t.error}),i.remove(),a&&a(),e.append(" "+n),e.show())})}function tvly_update(){$.get("/ajax/status_card.html",function(t){$(".statuscol").html(t),tvly_reg_handlers(),upd_journey_data(),setTimeout(tvly_update,4e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),upd_countdown(),setTimeout(tvly_update,5e3)})}function tvly_update_public(){var t,e=0;$(".publicstatuscol").each(function(){t=$(this).data("user"),e=$(this).data("profile")}),$.get("/ajax/status/"+t+".html",{token:j_token,profile:e},function(t){$(".publicstatuscol").html(t),upd_journey_data(),setTimeout(tvly_update_public,4e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),upd_countdown(),setTimeout(tvly_update_public,5e3)})}function tvly_update_timeline(){$.get("/timeline/in-transit",{ajax:1},function(t){$(".timeline-in-transit").html(t),setTimeout(tvly_update_timeline,6e4)}).fail(function(){$(".sync-failed-marker").css("display","block"),setTimeout(tvly_update_timeline,1e4)})}function tvly_journey_progress(){var t=Date.now()/1e3,e=0;if(0<j_duration){for(stop in 1<(e=(e=1-(j_arrival-t)/j_duration)<0?0:e)&&(e=1),$(".progress .determinate").css("width",100*e+"%"),j_stops){var a=j_stops[stop][0],n=j_stops[stop][1],i=j_stops[stop][2],o=j_stops[stop][3],r=j_stops[stop][4];if(a==j_dest){$(".next-stop").html("");break}if(0!=i&&0<i-t){$(".next-stop").html(a+"<br/>"+hhmm(i)+odelay(n,i));break}if(0!=r&&0<r-t){0!=i?$(".next-stop").html(a+"<br/>"+hhmm(i)+" → "+hhmm(r)+odelay(o,r)):$(".next-stop").html(a+"<br/>"+hhmm(r)+odelay(o,r));break}}setTimeout(tvly_journey_progress,5e3)}}function tvly_reg_handlers(){$(".action-checkin").click(function(){var t=$(this),e={action:"checkin",dbris:t.data("dbris"),hafas:t.data("hafas"),motis:t.data("motis"),station:t.data("station"),train:t.data("train"),suffix:t.data("suffix"),dest:t.data("dest"),ts:t.data("ts")};tvly_run(t,e)}),$(".action-checkout").click(function(){var t=$(this),e={action:"checkout",dbris:t.data("dbris"),hafas:t.data("hafas"),motis:t.data("motis"),station:t.data("station"),force:t.data("force")};tvly_run(t,e,function(){t.data("force")||(t.append(" – Ohne Echtzeitdaten auschecken?"),t.data("force",!0))})}),$(".action-undo").click(function(){var t=$(this),e=Date.now()/1e3,a=parseInt(t.data("checkints")),n={action:"undo",undo_id:t.data("id")},i=!0;(i=900<e-a?confirm("Checkin wirklich rückgängig machen? Er kann ggf. nicht wiederholt werden."):i)&&tvly_run(t,n)}),$(".action-cancelled-from").click(function(){var t=$(this),e={action:"cancelled_from",dbris:t.data("dbris"),hafas:t.data("hafas"),motis:t.data("motis"),station:t.data("station"),ts:t.data("ts"),train:t.data("train")};tvly_run(t,e)}),$(".action-cancelled-to").click(function(){var t=$(this),e={action:"cancelled_to",dbris:t.data("dbris"),hafas:t.data("hafas"),motis:t.data("motis"),station:t.data("station"),force:!0};tvly_run(t,e)}),$(".action-delete").click(function(){var t=$(this),e={action:"delete",id:t.data("id"),checkin:t.data("checkin"),checkout:t.data("checkout")};confirm("Diese Fahrt wirklich löschen? Der Eintrag wird sofort aus der Datenbank entfernt und kann nicht wiederhergestellt werden.")&&tvly_run(t,e)}),$(".action-share").click(function(){var t=$(this).data("text"),e=$(this).data("url");navigator.share?(shareObj={text:t},e&&(shareObj.url=e),navigator.share(shareObj)):(e&&(t+=" "+e),(e=document.createElement("textarea")).value=t,e.setAttribute("readonly",""),e.style.position="absolute",e.style.left="-9999px",document.body.appendChild(e),e.select(),e.setSelectionRange(0,99999),document.execCommand("copy"),document.body.removeChild(e),M.toast({html:"Text kopiert: „"+t+"“"}))})}$(document).ready(function(){tvly_reg_handlers(),$(".statuscol .autorefresh").length&&(upd_journey_data(),setTimeout(tvly_update,4e4),setTimeout(tvly_journey_progress,5e3)),$(".publicstatuscol .autorefresh").length&&(upd_journey_data(),setTimeout(tvly_update_public,4e4),setTimeout(tvly_journey_progress,5e3)),$(".timeline-in-transit .autorefresh").length&&setTimeout(tvly_update_timeline,6e4),$("a[href]").click(function(){$("nav .preloader-wrapper").addClass("active")}),$('a[href="#now"]').keydown(function(t){13==t.keyCode&&(t.preventDefault(),t.target.click())}),$('a[href="#now"]').click(function(t){t.preventDefault(),$("nav .preloader-wrapper").removeClass("active"),(now_el=$("#now")[0]).previousElementSibling.querySelector(".dep-time").focus(),now_el.scrollIntoView({behavior:"smooth",block:"center"})});var t=document.querySelectorAll(".carousel");M.Carousel.init(t,{fullWidth:!0,indicators:!0})}); diff --git a/public/static/manifest.json b/public/static/manifest.json index 67f1dfc..30cc7bc 100644 --- a/public/static/manifest.json +++ b/public/static/manifest.json @@ -3,27 +3,27 @@ "short_name": "Travelynx", "scope": "/", "icons": [{ - "src": "/static/v82/icons/icon-128x128.png", + "src": "/static/v87/icons/icon-128x128.png", "sizes": "128x128", "type": "image/png" }, { - "src": "/static/v82/icons/icon-144x144.png", + "src": "/static/v87/icons/icon-144x144.png", "sizes": "144x144", "type": "image/png" }, { - "src": "/static/v82/icons/icon-152x152.png", + "src": "/static/v87/icons/icon-152x152.png", "sizes": "152x152", "type": "image/png" }, { - "src": "/static/v82/icons/icon-192x192.png", + "src": "/static/v87/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/static/v82/icons/icon-256x256.png", + "src": "/static/v87/icons/icon-256x256.png", "sizes": "256x256", "type": "image/png" }, { - "src": "/static/v82/icons/icon-512x512.png", + "src": "/static/v87/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" }], diff --git a/public/static/v81 b/public/static/v86 index 945c9b4..945c9b4 120000 --- a/public/static/v81 +++ b/public/static/v86 diff --git a/public/static/v82 b/public/static/v87 index 945c9b4..945c9b4 120000 --- a/public/static/v82 +++ b/public/static/v87 diff --git a/sass/src/common/local.scss b/sass/src/common/local.scss index bffe9a6..2ba0ffa 100644 --- a/sass/src/common/local.scss +++ b/sass/src/common/local.scss @@ -209,30 +209,30 @@ ul.route-history > li { min-width: 6ch; margin: 0 auto; - &.Bus, &.RUF, &.AST { + &.Bus, &.BUS, &.RUF, &.AST { background-color: #a3167e; border-radius: 5rem; padding: .2rem .5rem; } - &.STR, &.Tram, &.Str, &.Strb, &.STB { + &.STR, &.Tram, &.TRAM, &.Str, &.Strb, &.STB { background-color: #c5161c; border-radius: 5rem; padding: .2rem .5rem; } - &.S, &.RS, &.RER, &.SKW { + &.S, &.RS, &.RER, &.SKW, &.METRO { background-color: #008d4f; border-radius: 5rem; padding: .2rem .5rem; } - &.U, &.M { + &.U, &.M, &.SUBWAY { background-color: #014e8d; border-radius: 5rem; padding: .2rem .5rem; } - &.RE, &.IRE, &.REX { + &.RE, &.IRE, &.REX, &.REGIONAL_FAST_RAIL { background-color: #ff4f00; } - &.RB, &.MEX, &.TER, &.R { + &.RB, &.MEX, &.TER, &.R, &.REGIONAL_RAIL { background-color: #1f4a87; } // DE @@ -242,7 +242,9 @@ ul.route-history > li { // FR &.TGV, &.OGV, &.EST, // PL - &.TLK, &.EIC { + &.TLK, &.EIC, + // MOTIS + &.HIGHSPEED_RAIL, &.LONG_DISTANCE { background-color: #ff0404; font-weight: 900; font-style: italic; @@ -251,7 +253,7 @@ ul.route-history > li { &.RJ, &.RJX { background-color: #c63131; } - &.NJ, &.EN { + &.NJ, &.EN, &.NIGHT_RAIL { background-color: #29255b; } &.WB { @@ -275,6 +277,17 @@ ul.route-history > li { } } +.status-card-progress-annot { + padding-bottom: 2ex; + border-bottom: 2px dashed #808080; +} + +.timeline-in-transit { + .status-card-progress-annot { + border-bottom: none; + } +} + @media screen and (max-width: 600px) { .collection.departures li { diff --git a/t/22-transit-visibility.t b/t/22-transit-visibility.t index 90f57d1..8a68f5c 100644 --- a/t/22-transit-visibility.t +++ b/t/22-transit-visibility.t @@ -108,10 +108,10 @@ sub test_intransit_visibility { $opt{effective_visibility_str}, $desc ); if ( $opt{public} ) { - $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN 667}); + $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN\s*667}); $t->get_ok('/ajax/status/test1.html')->status_is(200) - ->content_like(qr{DPN 667}); - $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); + $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN\s*667}); } else { $t->get_ok('/status/test1')->status_is(200) @@ -124,9 +124,9 @@ sub test_intransit_visibility { if ( $opt{with_token} ) { $t->get_ok("/status/test1/$token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/status/test1/$token")->status_is(200) @@ -142,10 +142,10 @@ sub test_intransit_visibility { # users can see their own status if visibility is >= followrs if ( $opt{effective_visibility} >= 60 ) { - $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN 667}); + $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN\s*667}); $t->get_ok('/ajax/status/test1.html')->status_is(200) - ->content_like(qr{DPN 667}); - $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); + $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN\s*667}); } else { $t->get_ok('/status/test1')->status_is(200) @@ -159,9 +159,9 @@ sub test_intransit_visibility { # users can see their own status with token if visibility is >= unlisted if ( $opt{effective_visibility} >= 30 ) { $t->get_ok("/status/test1/$token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/status/test1/$token")->status_is(200) @@ -178,10 +178,10 @@ sub test_intransit_visibility { # uid2 can see uid1 if visibility is >= followers if ( $opt{effective_visibility} >= 60 ) { - $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN 667}); + $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN\s*667}); $t->get_ok('/ajax/status/test1.html')->status_is(200) - ->content_like(qr{DPN 667}); - $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); + $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN\s*667}); } else { $t->get_ok('/status/test1')->status_is(200) @@ -195,9 +195,9 @@ sub test_intransit_visibility { # uid2 can see uid1 with token if visibility is >= unlisted if ( $opt{effective_visibility} >= 30 ) { $t->get_ok("/status/test1/$token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/status/test1/$token")->status_is(200) @@ -214,10 +214,10 @@ sub test_intransit_visibility { # uid3 can see uid1 if visibility is >= travelynx if ( $opt{effective_visibility} >= 80 ) { - $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN 667}); + $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN\s*667}); $t->get_ok('/ajax/status/test1.html')->status_is(200) - ->content_like(qr{DPN 667}); - $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); + $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN\s*667}); } else { $t->get_ok('/status/test1')->status_is(200) @@ -231,9 +231,9 @@ sub test_intransit_visibility { # uid3 can see uid1 with token if visibility is >= unlisted if ( $opt{effective_visibility} >= 30 ) { $t->get_ok("/status/test1/$token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/status/test1/$token")->status_is(200) diff --git a/t/23-journey-visibility.t b/t/23-journey-visibility.t index 58b305a..1cc7e64 100644 --- a/t/23-journey-visibility.t +++ b/t/23-journey-visibility.t @@ -111,7 +111,7 @@ sub test_journey_visibility { if ( $opt{public} ) { $t->get_ok("/p/test1/j/$jid")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/p/test1/j/$jid")->status_is(404) @@ -120,7 +120,7 @@ sub test_journey_visibility { if ( $opt{with_token} ) { $t->get_ok("/p/test1/j/$jid$token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/p/test1/j/$jid$token")->status_is(404) @@ -135,7 +135,7 @@ sub test_journey_visibility { # users can see their own status if visibility is >= followrs if ( $opt{effective_visibility} >= 60 ) { $t->get_ok("/p/test1/j/$jid")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/p/test1/j/$jid")->status_is(404) @@ -145,7 +145,7 @@ sub test_journey_visibility { # users can see their own status with token if visibility is >= unlisted if ( $opt{effective_visibility} >= 30 ) { $t->get_ok("/p/test1/j/$jid$token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/p/test1/j/$jid$token")->status_is(404) @@ -161,7 +161,7 @@ sub test_journey_visibility { # uid2 can see uid1 if visibility is >= followers if ( $opt{effective_visibility} >= 60 ) { $t->get_ok("/p/test1/j/$jid")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/p/test1/j/$jid")->status_is(404) @@ -171,7 +171,7 @@ sub test_journey_visibility { # uid2 can see uid1 with token if visibility is >= unlisted if ( $opt{effective_visibility} >= 30 ) { $t->get_ok("/p/test1/j/$jid$token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/p/test1/j/$jid$token")->status_is(404) @@ -187,7 +187,7 @@ sub test_journey_visibility { # uid3 can see uid1 if visibility is >= travelynx if ( $opt{effective_visibility} >= 80 ) { $t->get_ok("/p/test1/j/$jid")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/p/test1/j/$jid")->status_is(404) @@ -197,7 +197,7 @@ sub test_journey_visibility { # uid3 can see uid1 with token if visibility is >= unlisted if ( $opt{effective_visibility} >= 30 ) { $t->get_ok("/p/test1/j/$jid$token")->status_is(200) - ->content_like(qr{DPN 667}); + ->content_like(qr{DPN\s*667}); } else { $t->get_ok("/p/test1/j/$jid$token")->status_is(404) diff --git a/t/24-past-visibility.t b/t/24-past-visibility.t index 935ab6c..cf981b9 100644 --- a/t/24-past-visibility.t +++ b/t/24-past-visibility.t @@ -116,11 +116,11 @@ sub test_history_visibility { if ( $opt{public} ) { $t->get_ok('/p/test1')->status_is(200) - ->content_like( qr{DPN 667}, "public $desc" ); + ->content_like( qr{DPN\s*667}, "public $desc" ); } else { $t->get_ok('/p/test1')->status_is(200) - ->content_unlike( qr{DPN 667}, "public $desc" ); + ->content_unlike( qr{DPN\s*667}, "public $desc" ); } login( @@ -130,11 +130,11 @@ sub test_history_visibility { if ( $opt{self} ) { $t->get_ok('/p/test1')->status_is(200) - ->content_like( qr{DPN 667}, "self $desc" ); + ->content_like( qr{DPN\s*667}, "self $desc" ); } else { $t->get_ok('/p/test1')->status_is(200) - ->content_unlike( qr{DPN 667}, "self $desc" ); + ->content_unlike( qr{DPN\s*667}, "self $desc" ); } logout(); @@ -145,11 +145,11 @@ sub test_history_visibility { if ( $opt{followers} ) { $t->get_ok('/p/test1')->status_is(200) - ->content_like( qr{DPN 667}, "follower $desc" ); + ->content_like( qr{DPN\s*667}, "follower $desc" ); } else { $t->get_ok('/p/test1')->status_is(200) - ->content_unlike( qr{DPN 667}, "follower $desc" ); + ->content_unlike( qr{DPN\s*667}, "follower $desc" ); } logout(); @@ -160,11 +160,11 @@ sub test_history_visibility { if ( $opt{travelynx} ) { $t->get_ok('/p/test1')->status_is(200) - ->content_like( qr{DPN 667}, "travelynx $desc" ); + ->content_like( qr{DPN\s*667}, "travelynx $desc" ); } else { $t->get_ok('/p/test1')->status_is(200) - ->content_unlike( qr{DPN 667}, "travelynx $desc" ); + ->content_unlike( qr{DPN\s*667}, "travelynx $desc" ); } logout(); diff --git a/templates/_backend_line.html.ep b/templates/_backend_line.html.ep index e640cfe..00496d3 100644 --- a/templates/_backend_line.html.ep +++ b/templates/_backend_line.html.ep @@ -6,7 +6,7 @@ % } % if ($backend->{has_area}) { <br/> - <a href="https://dbf.finalrewind.org/coverage/<%= uc($backend->{type}) %>/<%= $backend->{name} %>"><%= join(q{, }, @{$backend->{regions} // []}) || '[Karte]' %></a> + <a href="https://dbf.finalrewind.org/coverage/<%= $backend->{type} %>/<%= $backend->{name} %>"><%= join(q{, }, @{$backend->{regions} // []}) || '[Karte]' %></a> % } % elsif ($backend->{regions}) { <br/> diff --git a/templates/_checked_in.html.ep b/templates/_checked_in.html.ep index 797ff57..016e56b 100644 --- a/templates/_checked_in.html.ep +++ b/templates/_checked_in.html.ep @@ -68,7 +68,7 @@ % } % if (my $wr = $journey->{wagonorder}) { <br/> - <!-- <a href="https://dbf.finalrewind.org/carriage-formation?<%= $journey->{train_no} %>/<%= $journey->{sched_departure}->strftime('%Y%m%d%H%M') %>?e=<%= $journey->{dep_direction} // q{} %>"> --> + <a href="https://dbf.finalrewind.org/carriage-formation?<%= join('&', map { $_ . '=' . $journey->{extra_data}{wagonorder_param}{$_} } sort keys %{$journey->{extra_data}{wagonorder_param}}) %>&e=<%= $journey->{dep_direction} // q{} %>"> % my $direction = $wr->direction == 100 ? '→' : '←'; % my $rev = 0; % if ($journey->{dep_direction}) { @@ -111,7 +111,7 @@ % } % } %= $direction - <!-- </a> --> + </a> % } </div> <div class="progress" style="height: 1ex;"> @@ -128,7 +128,7 @@ % } </div> <div style="float: right; text-align: right;"> - <b><a href="<%= resolve_sb_template($user->{sb_template}, name => $journey->{arr_name}, eva => $journey->{arr_eva}, tt => $journey->{train_type} // q{x}, tn => $journey->{train_no}, id => $journey->{train_id}, hafas => $journey->{is_hafas} ? $journey->{backend_name} : q{}) %>" class="unmarked"><%= $journey->{arr_name} %></a></b><br/> + <b><a href="<%= resolve_sb_template($user->{sb_template}, name => $journey->{arr_name}, eva => $journey->{arr_eva}, tt => $journey->{train_type} // q{x}, tn => $journey->{train_no}, id => $journey->{train_id} =~ s{[ #|]}{x}gr, dbris => $journey->{is_dbris} ? $journey->{backend_name} : q{}, hafas => $journey->{is_hafas} ? $journey->{backend_name} : q{}, is_iris => $journey->{is_iris}) %>" class="unmarked"><%= $journey->{arr_name} %></a></b><br/> % if ($journey->{real_arrival}->epoch) { <b><%= $journey->{real_arrival}->strftime('%H:%M') %></b> % if ($journey->{real_arrival}->epoch != $journey->{sched_arrival}->epoch) { @@ -242,13 +242,7 @@ <a class="tablerow action-checkout" data-station="<%= $station->[1] // $station->[0] %>"> <span><%= $station->[0] %></span> <span> - % if ($station->[2]{load}{SECOND}) { - % my ($first, $second) = load_icon($station->[2]{load}); - % if ($first ne 'help_outline') { - <i class="material-icons tiny" aria-hidden="true"><%= $first %></i> - % } - <i class="material-icons tiny" aria-hidden="true"><%= $second %></i> - % } + %= include '_show_load_icons', station => $station % if ($station->[2]{isCancelled}) { entfällt % } @@ -327,15 +321,16 @@ % if (@{stash('timeline') // []}) { %= include '_timeline_link', timeline => stash('timeline'), from_checkin => 1 % } - % if ($journey->{arr_name}) { + % if ($journey->{arr_name} and @{$journey->{extra_data}{him_msg} // []}) { <div class="card" style="margin-top: <%= scalar @{stash('timeline') // []} ? '1.5rem' : '3em' %>;"> <div class="card-content"> - <span class="card-title">Details</span> + <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> + <span class="card-title">Meldungen</span> % if (@{$journey->{extra_data}{him_msg} // []}) { <p style="margin-bottom: 2ex;"> <ul> % for my $message (@{$journey->{extra_data}{him_msg} // []}) { - <li> <i class="material-icons tiny">info</i> <%= $message->{header} %> <%= $message->{lead} %></li> + <li> <i class="material-icons tiny"><%= ($message->{prio} and $message->{prio} eq 'HOCH') ? 'warning' : 'info' %></i> <%= $message->{header} %> <%= $message->{lead} %></li> % } </ul> </p> @@ -355,26 +350,16 @@ </p> % } </div> - <div class="card-action"> - % my $url = 'https://bahn.expert/details/'; - % if ($journey->{train_id} =~ m{[|]}) { - % if ($journey->{train_type} and $journey->{train_no}) { - % $url .= $journey->{train_type} . ' ' . $journey->{train_no}; - % } - % $url .= '/' . $journey->{sched_departure}->epoch . '000?jid=' . $journey->{train_id} =~ s{#}{%23}gr; - % } - % else { - % $url .= $journey->{train_type} . ' ' . $journey->{train_no} . '/' . $journey->{sched_departure}->epoch . '000?station=' . $journey->{dep_eva}; - % } - % if ($journey->{backend_id} <= 1) { - <a style="margin-right: 0;" href="<%= $url %>"><i class="material-icons left" aria-hidden="true">timeline</i> Zuglauf</a> - % } - % else { - - % } - % if ($journey->{extra_data}{trip_id}) { - <a class="right" style="margin-right: 0;" href="https://dbf.finalrewind.org/map/<%= $journey->{extra_data}{trip_id} =~ s{#}{%23}gr %>/<%= $journey->{train_line} || 0 %>?hafas=<%= $journey->{backend_name} // 'DB' %>&from=<%= $journey->{dep_name} %>&to=<%= $journey->{arr_name} %>&dark=<%= (session('theme') and session('theme') eq 'dark') ? 1 : 0 %>"><i class="material-icons left" aria-hidden="true">map</i> Karte</a> - % } + </div> + % } + % if ($journey->{arr_name}) { + <div class="card" style="margin-top: 3em;"> + <div class="card-content"> + <i class="material-icons small right sync-failed-marker grey-text" style="display: none;">sync_problem</i> + <span class="card-title">Karte</span> + <div id="map" style="height: 70vh;"> + </div> + %= include '_map', with_map_header => 0, station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') </div> </div> <div class="card" style="margin-top: 3em;"> @@ -387,13 +372,7 @@ <a class="action-checkout tablerow" style="<%= $is_dest? 'font-weight: bold;' : '' %>" data-station="<%= $station->[1] // $station->[0] %>"> <span><%= $station->[0] %></span> <span> - % if ($station->[2]{load}{SECOND}) { - % my ($first, $second) = load_icon($station->[2]{load}); - % if ($first ne 'help_outline') { - <i class="material-icons tiny" aria-hidden="true"><%= $first %></i> - % } - <i class="material-icons tiny" aria-hidden="true"><%= $second %></i> - % } + %= include '_show_load_icons', station => $station % if ($station->[2]{isCancelled}) { entfällt % } @@ -408,7 +387,7 @@ % } </span> </a> - <a class="nonflex" href="<%= resolve_sb_template($user->{sb_template}, name => $station->[0], eva => $station->[1], tt => $journey->{train_type} // q{x}, tn => $journey->{train_no}, id => $journey->{train_id}, hafas => $journey->{is_hafas} ? $journey->{backend_name} : q{}) %>"><i class="material-icons tiny"><%= $journey->{is_hafas} ? 'directions' : 'train' %></i></a> + <a class="nonflex" href="<%= resolve_sb_template($user->{sb_template}, name => $station->[0], eva => $station->[1], tt => $journey->{train_type} // q{x}, tn => $journey->{train_no}, id => $journey->{train_id} =~ s{[ #|]}{x}gr, dbris => $journey->{is_dbris} ? $journey->{backend_name} : q{}, hafas => $journey->{is_hafas} ? $journey->{backend_name} : q{}, is_iris => $journey->{is_iris}) %>"><i class="material-icons tiny"><%= $journey->{is_hafas} ? 'directions' : 'train' %></i></a> % } </div> </div> diff --git a/templates/_departures_dbris.html.ep b/templates/_departures_dbris.html.ep new file mode 100644 index 0000000..dbd1a70 --- /dev/null +++ b/templates/_departures_dbris.html.ep @@ -0,0 +1,55 @@ +<ul class="collection departures"> +% my $orientation_bar_shown = param('train'); +% my $now_epoch = now->epoch; +% for my $result (@{$results}) { + % my $row_class = ''; + % my $link_class = 'action-checkin'; + % if ($result->is_cancelled) { + % $row_class = "cancelled"; + % $link_class = 'action-cancelled-from'; + % } + % if (not $orientation_bar_shown and $result->dep->epoch < $now_epoch) { + % $orientation_bar_shown = 1; + <li class="collection-item" id="now"> + <strong class="dep-time"> + %= now->strftime('%H:%M') + </strong> + <strong>— Anfragezeitpunkt —</strong> + </li> + % } + <li class="collection-item <%= $link_class %> <%= $row_class %>" + data-dbris="<%= $dbris %>" + data-station="<%= $result->stop_eva %>" + data-train="<%= $result->id %>" + data-suffix="<%= $result->maybe_line_no %>" + data-ts="<%= ($result->sched_dep // $result->dep)->epoch %>" + > + <a class="dep-time" href="#"> + %= $result->dep->strftime('%H:%M') + % if ($result->delay) { + (<%= sprintf('%+d', $result->delay) %>) + % } + % elsif (not defined $result->delay and not $result->is_cancelled) { + <i class="material-icons" aria-label="Keine Echtzeitdaten vorhanden" style="font-size: 16px;">gps_off</i> + % } + </a> + <span class="dep-line <%= $result->type // q{} %>"> + %= $result->line + </span> + <span class="dep-dest"> + % if ($result->is_cancelled) { + Fahrt nach <%= $result->destination // $result->via_last %> entfällt + % } + % else { + %= $result->destination // $result->via_last + % for my $checkin (@{$checkin_by_train->{$result->id} // []}) { + <span class="followee-checkin"> + <i class="material-icons tiny" aria-label="Eine Person, der du folgst, ist hier eingecheckt">people</i> + <%= $checkin->{followee_name} %> → <%= $checkin->{arr_name} // '???' %> + </span> + % } + % } + </span> + </li> +% } +</ul> diff --git a/templates/_departures_motis.html.ep b/templates/_departures_motis.html.ep new file mode 100644 index 0000000..2ebc5de --- /dev/null +++ b/templates/_departures_motis.html.ep @@ -0,0 +1,54 @@ +<ul class="collection departures"> +% my $orientation_bar_shown = param('train'); +% my $now_epoch = now->epoch; +% for my $result (@{$results}) { + % my $row_class = ''; + % my $link_class = 'action-checkin'; + % if ($result->is_cancelled) { + % $row_class = "cancelled"; + % $link_class = 'action-cancelled-from'; + % } + % if (not $orientation_bar_shown and $result->stopover->departure->epoch < $now_epoch) { + % $orientation_bar_shown = 1; + <li class="collection-item" id="now"> + <strong class="dep-time"> + %= now->strftime('%H:%M') + </strong> + <strong>— Anfragezeitpunkt —</strong> + </li> + % } + <li class="collection-item <%= $link_class %> <%= $row_class %>" + data-motis="<%= $motis %>" + data-station="<%= $result->stopover->stop->id %>" + data-train="<%= $result->id %>" + data-ts="<%= ($result->stopover->departure)->epoch %>" + > + <a class="dep-time" href="#"> + %= $result->stopover->departure->strftime('%H:%M') + % if ($result->stopover->delay) { + (<%= sprintf('%+d', $result->stopover->delay) %>) + % } + % elsif (not $result->stopover->is_realtime and not $result->stopover->is_cancelled) { + <i class="material-icons" aria-label="Keine Echtzeitdaten vorhanden" style="font-size: 16px;">gps_off</i> + % } + </a> + <span class="dep-line <%= $result->mode %>" style="background-color: #<%= $result->route_color // q{} %>;"> + %= $result->route_name + </span> + <span class="dep-dest"> + % if ($result->is_cancelled) { + Fahrt nach <%= $result->headsign %> entfällt + % } + % else { + %= $result->headsign + % for my $checkin (@{$checkin_by_train->{$result->id} // []}) { + <span class="followee-checkin"> + <i class="material-icons tiny" aria-label="Eine Person, der du folgst, ist hier eingecheckt">people</i> + <%= $checkin->{followee_name} %> → <%= $checkin->{arr_name} // '???' %> + </span> + % } + % } + </span> + </li> +% } +</ul> diff --git a/templates/_format_train.html.ep b/templates/_format_train.html.ep index 1d6acaa..e82f3f9 100644 --- a/templates/_format_train.html.ep +++ b/templates/_format_train.html.ep @@ -2,7 +2,9 @@ 🏳️🌈 % } <span class="dep-line <%= $journey->{train_type} // q{} %>"> - <%= $journey->{train_type} %> + % if (not $journey->{is_motis}) { + <%= $journey->{train_type} %> + % } <%= $journey->{train_line} // $journey->{train_no}%> </span> % if ($journey->{train_line}) { diff --git a/templates/_history_trains.html.ep b/templates/_history_trains.html.ep index cf998ab..7ae2a1d 100644 --- a/templates/_history_trains.html.ep +++ b/templates/_history_trains.html.ep @@ -17,7 +17,10 @@ <li class="collection-item"> <a href="<%= $detail_link %>"> <span class="dep-line <%= $travel->{type} // q{} %>"> - <%= $travel->{type} %> <%= $travel->{line} // $travel->{no}%> + % if (not $travel->{is_motis}) { + <%= $travel->{type} %> + % } + <%= $travel->{line} // $travel->{no}%> </span> </a> diff --git a/templates/_map.html.ep b/templates/_map.html.ep index daa16f0..223bd68 100644 --- a/templates/_map.html.ep +++ b/templates/_map.html.ep @@ -1,16 +1,18 @@ -<div class="row"> - <div class="col s12"> - <div id="map" style="height: 70vh;"> +% if (stash('with_map_header') // 1) { + <div class="row"> + <div class="col s12"> + <div id="map" style="height: 70vh;"> + </div> </div> </div> -</div> -<div class="row"> - <div class="col s12"> - <span style="color: #f03;">●</span> Ein-/Ausstiegsstation<br/> - <span style="color: #673ab7;">—</span> Streckenverlauf oder Luftlinie + <div class="row"> + <div class="col s12"> + <span style="color: #f03;">●</span> Ein-/Ausstiegsstation<br/> + <span style="color: #673ab7;">—</span> Streckenverlauf oder Luftlinie + </div> </div> -</div> +% } <script> var map = L.map('map').setView([51.306, 9.712], 6); diff --git a/templates/_public_status_card.html.ep b/templates/_public_status_card.html.ep index 6dda46d..33f4236 100644 --- a/templates/_public_status_card.html.ep +++ b/templates/_public_status_card.html.ep @@ -14,17 +14,14 @@ <a href="/p/<%= $name %>"><%= $name %></a> ist unterwegs % } <i class="material-icons right"><%= visibility_icon($journey->{effective_visibility_str}) %></i> - % if (stash('from_timeline') and $journey->{extra_data}{trip_id}) { - <a class="right" href="https://dbf.finalrewind.org/map/<%= $journey->{extra_data}{trip_id} =~ s{#}{%23}gr %>/<%= $journey->{train_line} || 0 %>?hafas=<%= $journey->{backend_name} // 'DB' %>&from=<%= $journey->{dep_name} %>&to=<%= $journey->{arr_name} // '' %>"><i class="material-icons">map</i></a> - % } % if (not $journey->{extra_data}{rt}) { <i class="material-icons right grey-text">gps_off</i> % } </span> % if ($privacy->{comments_visible} and $journey->{comment}) { - <p>„<%= $journey->{comment} %>“</p> + <div>„<%= $journey->{comment} %>“</div> % } - <p> + <div> % if (not stash('from_profile') and not stash('from_timeline')) { <div class="center-align"> %= include '_format_train', journey => $journey @@ -63,8 +60,8 @@ <div class="progress" style="height: 1ex;"> <div class="determinate" style="width: <%= sprintf('%.2f', 100 * ($journey->{journey_completion} // 0)); %>%;"></div> </div> - </p> - <p> + </div> + <div class="status-card-progress-annot"> <div style="float: left;"> <b><%= $journey->{dep_name} %></b><br/> <b><%= $journey->{real_departure}->strftime('%H:%M') %></b> @@ -95,20 +92,31 @@ % last; % } % if (($station->[2]{arr_countdown} // 0) > 0 and $station->[2]{arr}) { - <%= $station->[0] %><br/><%= $station->[2]{arr}->strftime('%H:%M') %> + %= $station->[0] + <br/> + %= $station->[2]{arr}->strftime('%H:%M') % if ($station->[2]{arr_delay}) { - %= sprintf('(%+d)', $station->[2]{arr_delay} / 60); + %= sprintf('(%+d)', $station->[2]{arr_delay} / 60) + % } + % if ($station->[2]{load}{SECOND}) { + <br/> + %= include '_show_load_icons', station => $station % } % last; % } % if (($station->[2]{dep_countdown} // 0) > 0 and $station->[2]{dep}) { - <%= $station->[0] %><br/> + %= $station->[0] + <br/> % if ($station->[2]{arr}) { <%= $station->[2]{arr}->strftime('%H:%M') %> → % } %= $station->[2]{dep}->strftime('%H:%M') % if ($station->[2]{dep_delay}) { - %= sprintf('(%+d)', $station->[2]{dep_delay} / 60); + %= sprintf('(%+d)', $station->[2]{dep_delay} / 60) + % } + % if ($station->[2]{load}{SECOND}) { + <br/> + %= include '_show_load_icons', station => $station % } % last; % } @@ -123,34 +131,46 @@ % } % if (($station->[2]{arr_countdown} // 0) > 0 and $station->[2]{arr}) { Nächster Halt:<br/> - <%= $station->[0] %><br/><%= $station->[2]{arr}->strftime('%H:%M') %> + %= $station->[0] + <br/> + %= $station->[2]{arr}->strftime('%H:%M') % if ($station->[2]{arr_delay}) { - %= sprintf('(%+d)', $station->[2]{arr_delay} / 60); + %= sprintf('(%+d)', $station->[2]{arr_delay} / 60) + % } + % if ($station->[2]{load}{SECOND}) { + <br/> + %= include '_show_load_icons', station => $station % } % last; % } % if (($station->[2]{dep_countdown} // 0) > 0 and $station->[2]{arr} and $station->[2]{dep}) { Aktueller Halt:<br/> - <%= $station->[0] %><br/> - <%= $station->[2]{arr}->strftime('%H:%M') %> → - <%= $station->[2]{dep}->strftime('%H:%M') %> + %= $station->[0] + <br/> + %= $station->[2]{arr}->strftime('%H:%M') + → + %= $station->[2]{dep}->strftime('%H:%M') % if ($station->[2]{dep_delay}) { - %= sprintf('(%+d)', $station->[2]{dep_delay} / 60); + %= sprintf('(%+d)', $station->[2]{dep_delay} / 60) + % } + % if ($station->[2]{load}{SECOND}) { + <br/> + %= include '_show_load_icons', station => $station % } % last; % } % } </div> - </p> + </div> % if ($journey->{extra_data}{cancelled_destination}) { - <p style="margin-bottom: 2ex;"> + <div style="margin-bottom: 2ex;"> <i class="material-icons tiny" aria-hidden="true">error</i> Der Halt an der Zielstation <b><%= $journey->{extra_data}{cancelled_destination} %></b> entfällt. - </p> + </div> % } % if (@{$journey->{messages} // []} > 0 and $journey->{messages}[0]) { - <p style="margin-bottom: 2ex;"> + <div style="margin-top: 2ex;"> <ul> % for my $message (reverse @{$journey->{messages} // []}) { % if ($journey->{sched_departure}->epoch - $message->[0]->epoch < 1800) { @@ -161,25 +181,95 @@ <li> <i class="material-icons tiny">info</i> <%= $message->[0]->strftime('%H:%M') %>: <%= $message->[1] %></li> % } </ul> - </p> + </div> % } - </div> - % if (not stash('from_timeline')) { - <div class="card-action"> - % if ($journey->{traewelling_url}) { - <a style="margin-right: 0;" href="<%= $journey->{traewelling_url} %>"><i class="material-icons left">timeline</i> Träwelling</a> - % } elsif ($journey->{backend_id} <= 1) { - % my $url = 'https://bahn.expert/details/' . $journey->{train_type} . ' ' . $journey->{train_no} . '/' . DateTime->now(time_zone => 'Europe/Berlin')->epoch . '000'; - <a style="margin-right: 0;" href="<%= $url %>"><i class="material-icons left">timeline</i> Zuglauf</a> + % if (@{$journey->{extra_data}{him_msg} // []}) { + <div style="margin-top: 2ex;"> + <ul> + % for my $message (@{$journey->{extra_data}{him_msg} // []}) { + % if (not stash('from_timeline') or $message->{prio} and $message->{prio} eq 'HOCH') { + <li> <i class="material-icons tiny"><%= ($message->{prio} and $message->{prio} eq 'HOCH') ? 'warning' : 'info' %></i> <%= $message->{header} %> <%= $message->{lead} %></li> + % } + % } + </ul> + </div> + % } + % if (stash('station_coordinates')) { + <div id="map" style="height: 70vh;"> + </div> + %= include '_map', with_map_header => 0, station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') + % } + % if ( @{$journey->{wagongroups} // []} ) { + % if (stash('from_timeline')) { + <div class="wagons" style="margin-top: 2ex;"> + % for my $wagongroup (@{$journey->{wagongroups}}) { + %= $wagongroup->{desc} // $wagongroup->{name} + % if ($wagongroup->{designation}) { + „<%= $wagongroup->{designation} %>“ + % } + % if ($wagongroup->{to}) { + → <%= $wagongroup->{to} %> + % } + <br/> + % } + </div> % } % else { - - % } - % if ($journey->{extra_data}{trip_id}) { - <a class="right" style="margin-right: 0;" href="https://dbf.finalrewind.org/map/<%= $journey->{extra_data}{trip_id} =~ s{#}{%23}gr %>/<%= $journey->{train_line} || 0 %>?hafas=<%= $journey->{backend_name} // 'DB' %>&from=<%= $journey->{dep_name} %>&to=<%= $journey->{arr_name} // '' %>"><i class="material-icons left">map</i> Karte</a> + <div class="wagons" style="margin-top: 2ex;"> + Wagen:<br/> + %= include '_wagons', wagongroups => $journey->{wagongroups}; + </div> % } - </div> - % } + % } + % if (not stash('from_timeline')) { + <div style="margin-top: 2ex;"> + Route:<br/> + % my $before = 1; + % my $within = 0; + % my $at_startstop = 0; + % for my $station (@{$journey->{route}}) { + % if (($station->[1] and $station->[1] == $journey->{dep_eva}) or $station->[0] eq $journey->{dep_name}) { + % $within = 1; $at_startstop = 1; + % } + % elsif ($journey->{arr_eva} and (($station->[1] and $station->[1] == $journey->{arr_eva}) or $station->[0] eq $journey->{arr_name})) { + % $within = 0; $at_startstop = 1; + % } + % else { + % $at_startstop = 0; + % } + <span style="color: #808080;"> + % if ($before and $station->[2]{sched_dep}) { + %= $station->[2]{sched_dep}->strftime('%H:%M') + % } + % elsif (not $before and $station->[2]{sched_arr}) { + %= $station->[2]{sched_arr}->strftime('%H:%M') + % } + </span> + % if ($at_startstop or $within) { + %= $station->[0] + % } + % else { + <span style="color: #808080;"><%= $station->[0] %></span> + % } + <span> + %= include '_show_load_icons', station => $station + </span> + <span style="color: #808080;"> + % if ($before and $station->[2]{rt_dep} and $station->[2]{dep_delay}) { + %= sprintf('%+d', $station->[2]{dep_delay} / 60) + % } + % elsif (not $before and $station->[2]{rt_arr} and $station->[2]{arr_delay}) { + %= sprintf('%+d', $station->[2]{arr_delay} / 60) + % } + </span> + % if (($station->[1] and $station->[1] == $journey->{dep_eva}) or $station->[0] eq $journey->{dep_name}) { + % $before = 0; + % } + <br/> + % } + </div> + % } + </div> </div> % } % else { @@ -192,7 +282,7 @@ % else { <span class="card-title"><a href="/p/<%= $name %>"><%= $name %></a> ist gerade nicht eingecheckt</span> % } - <p> + <div> % if ($journey->{arr_name}) { Zuletzt gesehen % if ($journey->{real_arrival}->epoch) { @@ -204,7 +294,7 @@ in <b><%= $journey->{arr_name} %></b> % } % } - </p> + </div> </div> </div> % } diff --git a/templates/_show_load_icons.html.ep b/templates/_show_load_icons.html.ep new file mode 100644 index 0000000..e69dab5 --- /dev/null +++ b/templates/_show_load_icons.html.ep @@ -0,0 +1,7 @@ +% if ($station->[2]{load}{SECOND}) { + % my ($first, $second) = load_icon($station->[2]{load}); + % if ($first ne 'help_outline') { + <i class="material-icons tiny" aria-hidden="true"><%= $first %></i> + % } + <i class="material-icons tiny" aria-hidden="true"><%= $second %></i> +% } diff --git a/templates/_wagons.html.ep b/templates/_wagons.html.ep index b4af3bc..4090f11 100644 --- a/templates/_wagons.html.ep +++ b/templates/_wagons.html.ep @@ -1,7 +1,10 @@ % for my $wagongroup (@{$wagongroups // []}) { - <%= $wagongroup->{name} %> + %= $wagongroup->{desc} // $wagongroup->{name} % my ($wagon_number) = ($wagongroup->{name} =~ m{ ^ ICE 0* (\d+) $ }x); - % if ($wagon_number and my $group_name = app->ice_name->{$wagon_number}) { + % if ($wagongroup->{designation}) { + „<%= $wagongroup->{designation} %>“ + % } + % elsif ($wagon_number and my $group_name = app->ice_name->{$wagon_number}) { „<%= $group_name %>“ % } als <b><%= $wagongroup->{type} // $journey->{type} %> <%= $wagongroup->{no} %></b> diff --git a/templates/about.html.ep b/templates/about.html.ep index d1fcb4b..3167d91 100644 --- a/templates/about.html.ep +++ b/templates/about.html.ep @@ -1,13 +1,16 @@ <div class="row"> <div class="col s12"> <a href="https://finalrewind.org/projects/travelynx">travelynx</a> v<%= stash('version') // '???' %><br/> - Entwickelt von <a href="https://finalrewind.org">derf</a><br/> - <a href="<%= app->config->{ref}{source} // 'https://github.com/derf/travelynx' %>">Quelltext</a> lizensiert unter AGPL v3<br/><br/> + Entwickelt von <a href="https://finalrewind.org">derf</a> + und <a href="https://github.com/derf/travelynx/graphs/contributors">weiteren</a><br/> + <a href="<%= app->config->{ref}{source} // 'https://git.finalrewind.org/travelynx' %>">Quelltext</a> lizensiert unter AGPL v3<br/><br/> Backends: + <a href="https://finalrewind.org/projects/Travel-Status-DE-DBRIS/">Travel::Status::DE::DBRIS</a> + v<%= $Travel::Status::DE::DBRIS::VERSION %>, + <a href="https://finalrewind.org/projects/Travel-Status-DE-HAFAS/">Travel::Status::DE::HAFAS</a> + v<%= $Travel::Status::DE::HAFAS::VERSION %> und <a href="https://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a> - v<%= $Travel::Status::DE::IRIS::VERSION %> und - <a href="https://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/">Travel::Status::DE::HAFAS</a> - v<%= $Travel::Status::DE::HAFAS::VERSION %><br/> + v<%= $Travel::Status::DE::IRIS::VERSION %><br/> Haltestellendaten © DB Station&Service AG, Europaplatz 1, @@ -16,6 +19,19 @@ </div> <div class="row"> + <div class="col s12"> + <p> + Travelynx ist ein kostenfreies, privat betriebenes Projekt ohne + Verfügbarkeitsgarantie. Unangekündigte Downtimes oder eine + kurzfristige Einstellung dieser Seite sind nicht vorgesehen, aber + möglich. Feature Requests, Bug Reports und sonstige Nachrichten + werden je nach Kapazität und Motivation zeitnah, verzögert oder gar + nicht bearbeitet / beantwortet. + </p> + </div> +</div> + +<div class="row"> <div class="col s12 m12 l4 center-align" style="margin-top: 1em;"> <a href="https://social.skyshaper.org/derf" class="waves-effect waves-light btn"><i class="material-icons left">message</i>Kontakt</a> </div> diff --git a/templates/account.html.ep b/templates/account.html.ep index 036fb35..e4bf38d 100644 --- a/templates/account.html.ep +++ b/templates/account.html.ep @@ -123,6 +123,8 @@ <tr> <th scope="row">Träwelling</th> <td> + Wird wegen Inkompatibilität zwischen bahn.de und transitous derzeit nicht unterstützt + <!-- <a href="/account/traewelling"><i class="material-icons">edit</i></a> % if (not ($traewelling->{token})) { <span style="color: #999999;">Nicht verknüpft</span> @@ -145,6 +147,7 @@ – Checkins in travelynx werden zu Träwelling weitergereicht % } % } + --> </td> </tr> % } diff --git a/templates/api_documentation.html.ep b/templates/api_documentation.html.ep index 4453286..099474c 100644 --- a/templates/api_documentation.html.ep +++ b/templates/api_documentation.html.ep @@ -129,12 +129,13 @@ {<br/> "token" : "<%= $uid %>-<%= $token->{travel} // 'TOKEN' %>",<br/> "action" : "checkin",<br/> - "hafas" : "DB", (HAFAS-Instanz – Default: Deutsche Bahn)<br/> + "dbris" : "bahn.de", (DBRIS-Instanz – Default: bahn.de)<br/> + "hafas" : null, (HAFAS-Instanz, falls verwendet, sonste null)<br/> "train" : {<br/> - "journeyID" : "1|1426396|4|80|19082023",<br/> + "journeyID" : "2|#VN#1#ST#1742845592#PI#0#ZI#315136#TA#0#DA#270325#1S#8000080#1T#1841#LS#8006486#LT#2024#PU#80#RT#1#CA#RE#ZE#10773#ZB#RE10773#PC#3#FR#8000080#FT#1841#TO#8006486#TT#2024#",<br/> }<br/> - "fromStation" : 651806, (Name oder EVA-Nummer)<br/> - "toStation" : 654645, (optional, Name oder EVA-Nummer)<br/> + "fromStation" : 8000080, (Name oder EVA-Nummer – bei bahn.de nur EVA-Nummer)<br/> + "toStation" : 8006486, (optional, Name oder EVA-Nummer – bei bahn.de nur EVA-Nummer)<br/> "comment" : "Beliebiger Text" (optional, überschreibt vorherigen Kommentar)<br/> } </p> diff --git a/templates/bad_gateway.html.ep b/templates/bad_gateway.html.ep index ca69188..07bf29e 100644 --- a/templates/bad_gateway.html.ep +++ b/templates/bad_gateway.html.ep @@ -6,7 +6,12 @@ <p> Das von travelynx genutzte Backend hat einen Fehler zurückgegeben. travelynx hat keine Möglichkeiten, diese Situation zu beheben. - Versuche es in ein paar Sekunden bis Minuten noch einmal. + % if (stash('select_new_backend')) { + Versuche es in ein paar Sekunden bis Minuten noch einmal oder <a href="/account/select_backend">wähle ein anderes Backend</a>. + % } + % else { + Versuche es in ein paar Sekunden bis Minuten noch einmal. + % } </p> </div> </div> diff --git a/templates/changelog.html.ep b/templates/changelog.html.ep index ad3291a..ced431a 100644 --- a/templates/changelog.html.ep +++ b/templates/changelog.html.ep @@ -2,6 +2,106 @@ <div class="row"> <div class="col s12 m1 l1"> + 2.13 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Experimentelle Unterstützung für Checkins via MOTIS-Backends + (derzeit transitous und RNV). Vielen Dank an <a href="https://github.com/networkException">networkException</a> + für die Implementierung der API und Einbindung in travelynx. + Träwelling-Synchronisierung ist noch nicht wiederhergestellt. + Time zones are currently somewhat wibbly-wobbly timey-wimey. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.12 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Kartografische Visualisierung der Route bei eigenen Checkins und auf + der Statusseite sowie Angaben zu Meldungen, Rollmaterial, Route und + Auslastung auf der Statusseite. Feinheiten wie die Markierung der + geschätzten aktuellen Zugposition oder eine regelmäßige + Aktualisierung ohne Zurücksetzen der Kartenansicht folgen später. + Die Kartenlinks zu dbf.finalrewind.org entfallen. + </p> + <p> + <i class="material-icons left" aria-label="Ankündigung">announcement</i> + Das IRIS-TTS-Backend der Deutschen Bahn wird wegen zunehmend + schlechter Datenanreicherunngsmöglichkeiten nicht mehr + weiterentwickelt. Bei Checkins per IRIS-TTS stehen regelmäßig keine + Echtzeitdaten und insbesondere bei Nebenbahnen auch keine + Kartendaten zur Verfügung. In diesem Fall fehlt auch die + ersatzwiese Visualisierung der Luftlinie zwischen den + Unterwegshalten. Dies betrifft auch die Visualisierung in der + Fahrtenkarte. + </p> + <p> + <i class="material-icons left" aria-label="Ankündigung">announcement</i> + Derzeit besteht wegen inkompatibler Backends keine + Synchronisierungsmöglichkeit zwischen Träwelling (transitous MOTIS) + und travelynx (DB IRIS-TTS / DB HAFAS / bahn.de). + MOTIS-Unterstützung in travelynx ist in Arbeit. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.11 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Neues Backend: bahn.de. Somit steht nach Abschaltung von DB HAFAS + und VRN HAFAS wieder ein Backend zur Verfügung, welches für + innerdeutschen Nah-, Regional- und Fernverkehr geeignet ist und + eine Synchronisierung mit Träwelling unterstützt. Teile der + Implementierung können noch unvollständig sein. Ebenso besteht die + Möglichkeit, dass es wegen Rate Limits auf Seiten von bahn.de nicht + immer zuverlässig nutzbar ist. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> + 2.10 + </div> + <div class="col s12 m11 l11"> + <p> + <i class="material-icons left" aria-label="Neues Feature">add</i> + Neue HAFAS-Backends: PKP, SaarVV. + </p> + <p> + <i class="material-icons left" aria-label="Bug">warning</i> Das DB + HAFAS-Backend wurde am 8. Januar 2025 abgeschaltet und wird von + travelynx daher seit v2.9.11 nicht mehr angeboten. Als vorläufiger + Ersatz bietet sich das VRN HAFAS-Backend an. Eine Wieder-Anbindung + der DB mittels Travel::Status::DE::DBRIS ist in Arbeit. Bis dahin + ist keine Synchronisierung mit Traewelling möglich. + </p> + <p> + <i class="material-icons left" aria-label="Administration">announcement</i> + Das PKP HAFAS befindet sich hinter einem GeoIP-Filter und wird + daher in travelynx-Installationen außerhalb von travelynx.de + standardmäßig nicht angeboten. Sofern die travelynx-Instanz auf + einer geeigneten IP-Adresse betrieben wird oder eine solche per + Proxy erreichbar ist, lässt es sich über einen Eintrag in + travelynx.conf aktivieren. Als Nebenwirkung davon kann auch auf + beliebige andere HAFAS-Instanzen bei Bedarf über einen + Instanz-spezifischen Proxy zugegriffen werden. + </p> + </div> +</div> + +<div class="row"> + <div class="col s12 m1 l1"> 2.9 </div> <div class="col s12 m11 l11"> diff --git a/templates/departures.html.ep b/templates/departures.html.ep index 0c563de..917973b 100644 --- a/templates/departures.html.ep +++ b/templates/departures.html.ep @@ -9,9 +9,15 @@ </div> <div class="col s4 center-align"> % my $self_link = url_for('sstation', station => $station // param('station')); - % if (param('hafas')) { + % if (param('dbris')) { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= param('dbris') %></a> + % } + % elsif (param('hafas')) { <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= param('hafas') %></a> % } + % elsif (param('motis')) { + <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= param('motis') %></a> + % } % else { % if ($user->{backend_id}) { <a href="/account/select_backend?redirect_to=<%= $self_link %>" class="btn-small btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= $user->{backend_name} %></a> @@ -30,7 +36,13 @@ <div class="card"> <div class="card-content"> <span class="card-title">Aktuell eingecheckt</span> - <p>In <%= $user_status->{train_type} %> <%= $user_status->{train_no} %> + <p>In + % if ( not $user_status->{is_motis} ) { + <%= $user_status->{train_type} %> + % } + + <%= $user_status->{train_line} // $user_status->{train_no} %> + % if ( $user_status->{arr_name}) { von <%= $user_status->{dep_name} %> nach <%= $user_status->{arr_name} %> % } @@ -93,8 +105,8 @@ <div class="row"> <div class="col s4 center-align"> - % if ($hafas) { - <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({hafas => $hafas, timestamp => $datetime->clone->subtract(hours => 1)->epoch}) %>"><i class="material-icons left" aria-hidden="true">chevron_left</i><span class="hide-on-small-only">früher</span></a> + % if ($dbris or $hafas or $motis) { + <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({dbris => $dbris, hafas => $hafas, timestamp => $datetime->clone->subtract(hours => 1)->epoch}) %>"><i class="material-icons left" aria-hidden="true">chevron_left</i><span class="hide-on-small-only">früher</span></a> % } </div> <div class="col s4 center-align"> @@ -103,8 +115,8 @@ % } </div> <div class="col s4 center-align"> - % if ($hafas) { - <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({hafas => $hafas, timestamp => $datetime->clone->add(hours => 1)->epoch}) %>"><span class="hide-on-small-only">später</span><i class="material-icons right" aria-hidden="true">chevron_right</i></a> + % if ($dbris or $hafas or $motis) { + <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({dbris => $dbris, hafas => $hafas, timestamp => $datetime->clone->add(hours => 1)->epoch}) %>"><span class="hide-on-small-only">später</span><i class="material-icons right" aria-hidden="true">chevron_right</i></a> % } </div> </div> @@ -134,20 +146,47 @@ Fahrt auswählen zum Einchecken. % } % else { - Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor - und maximal 120 Minuten nach Abfahrt möglich. + % if ($dbris or $hafas) { + Keine Abfahrten im ausgewählten Zeitfenster + (<%= $datetime->strftime('%d.%m.%Y %H:%M') %> ± 30min). + % } + % else { + Keine Abfahrten gefunden. Ein Checkin ist frühestens 30 Minuten vor + und maximal 120 Minuten nach Abfahrt möglich. + % } % } </p> % if (not $user_status->{checked_in} or ($can_check_out and $user_status->{arr_eva} and $user_status->{arrival_countdown} <= 0)) { - % if ($efa) { + % if ($dbris) { + %= include '_departures_dbris', results => $results, dbris => $dbris; + % } + % elsif ($efa) { %= include '_departures_efa', results => $results, efa => $efa; % } % elsif ($hafas) { %= include '_departures_hafas', results => $results, hafas => $hafas; % } + % elsif ($motis) { + %= include '_departures_motis', results => $results, motis => $motis; + % } % else { %= include '_departures_iris', results => $results; % } % } </div> </div> + +<div class="row"> + <div class="col s4 center-align"> + % if ($dbris or $hafas) { + <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({dbris => $dbris, hafas => $hafas, timestamp => $datetime->clone->subtract(hours => 1)->epoch}) %>"><i class="material-icons left" aria-hidden="true">chevron_left</i><span class="hide-on-small-only">früher</span></a> + % } + </div> + <div class="col s4 center-align"> + </div> + <div class="col s4 center-align"> + % if ($dbris or $hafas) { + <a class="btn-small" href="<%= url_for('sstation', station => param('station'))->query({dbris => $dbris, hafas => $hafas, timestamp => $datetime->clone->add(hours => 1)->epoch}) %>"><span class="hide-on-small-only">später</span><i class="material-icons right" aria-hidden="true">chevron_right</i></a> + % } + </div> +</div> diff --git a/templates/gateway_timeout.html.ep b/templates/gateway_timeout.html.ep index 86e844d..9cf8690 100644 --- a/templates/gateway_timeout.html.ep +++ b/templates/gateway_timeout.html.ep @@ -6,7 +6,12 @@ <p> Das von travelynx genutzte Backend hat nicht rechtzeitig reagiert. travelynx hat keine Möglichkeiten, diese Situation zu beheben. - Vrsuche es in ein paar Sekunden bis Minuten noch einmal. + % if (stash('select_new_backend')) { + Versuche es in ein paar Sekunden bis Minuten noch einmal oder <a href="/account/select_backend">wähle ein anderes Backend</a>. + % } + % else { + Versuche es in ein paar Sekunden bis Minuten noch einmal. + % } </p> </div> </div> diff --git a/templates/journey.html.ep b/templates/journey.html.ep index c13da5f..e97c729 100644 --- a/templates/journey.html.ep +++ b/templates/journey.html.ep @@ -177,6 +177,16 @@ </td> </tr> % } + % if ($journey->{user_data}{him_msg} and @{$journey->{user_data}{him_msg}}) { + <tr> + <th scope="row">Meldungen</th> + <td> + % for my $message (@{$journey->{user_data}{him_msg} // []}) { + <i class="material-icons tiny"><%= ($message->{prio} and $message->{prio} eq 'HOCH') ? 'warning' : 'info' %></i> <%= $message->{header} %> <%= $message->{lead} %><br/> + % } + </td> + </tr> + % } % if ($journey->{user_data} and $journey->{user_data}{comment}) { <tr> <th scope="row">Kommentar</th> @@ -269,7 +279,7 @@ % } <div class="row"> <div class="col s12 grey-text"> - <i class="material-icons tiny" aria-hidden="true"><%= $journey->{is_iris} ? 'train' : 'directions' %></i> + <i class="material-icons tiny" aria-hidden="true"><%= $journey->{is_hafas} ? 'directions' : 'train' %></i> %= $journey->{backend_name} || 'IRIS' #<%= $journey->{id} %> </div> diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep index 68ff41c..56aa8ff 100644 --- a/templates/landingpage.html.ep +++ b/templates/landingpage.html.ep @@ -57,21 +57,17 @@ <div class="card-content"> <span class="card-title">Hallo, <%= $user->{name} %>!</span> <p>Du bist gerade nicht eingecheckt.</p> - <div class="geolocation" data-recent="<%= join('|', map { $_->{eva} . ';' . $_->{name} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>" data-backend="<%= $user->{backend_id} %>"> + <div class="geolocation" data-recent="<%= join('|', map { $_->{external_id_or_eva} . ';' . $_->{name} . ';' . $_->{dbris} . ';' . $_->{motis} . ';' . $_->{hafas} } @{stash('recent_targets') // []} ) %>" data-backend="<%= $user->{backend_id} %>"> <a class="btn waves-effect waves-light btn-flat request">Stationen in der Umgebung abfragen</a> </div> + %= hidden_field backend_dbris => $user->{backend_dbris} <div class="input-field"> %= text_field 'station', id => 'station', class => 'autocomplete contrast-color-text', autocomplete => 'off', required => undef <label for="station">Manuelle Eingabe</label> </div> </div> <div class="card-action"> - % if ($user->{backend_id}) { - <a href="/account/select_backend?redirect_to=/" class="btn btn-flat"><i class="material-icons left" aria-hidden="true">directions</i><%= $user->{backend_name} %></a> - % } - % else { - <a href="/account/select_backend?redirect_to=/" class="btn btn-flat"><i class="material-icons left" aria-hidden="true">train</i>IRIS</a> - % } + <a href="/account/select_backend?redirect_to=/" class="btn btn-flat"><i class="material-icons left" aria-hidden="true"><%= $user->{backend_hafas} ? 'directions' : 'train' %></i><%= $user->{backend_name} // 'IRIS' %></a> <button class="btn right waves-effect waves-light btn-flat" type="submit" name="action" value="departures"> <i class="material-icons left" aria-hidden="true">send</i> Abfahrten @@ -82,6 +78,25 @@ % } </div> </div> + % if (not $user->{backend_name}) { + <div class="row"> + <div class="col s12"> + <div class="card purple white-text"> + <div class="card-content"> + <span class="card-title">Legacy-Backend ausgewählt</span> + <p> + Das aktuell aktive IRIS-Backend wird nicht mehr weiterentwickelt und voraussichtlich bald von der Deutschen Bahn abgeschaltet. + Schon jetzt ist die Datenqualität wegen zunehmend schlechter Datenaufbereitungsmöglichkeiten oft unzureichend. + Das bahn.de-Backend ist in fast jeder Hinsicht besser geeignet; lediglich bei Verspätungs- und Servicemeldungen ist es geringfügig weniger detailliert und Checkin-Vorschläge werden derzeit nicht unterstützt. + </p> + </div> + <div class="card-action"> + <a class="btn btn-flat" href="/account/select_backend?redirect_to=/">Backend wechseln</a> + </div> + </div> + </div> + </div> + % } <h2 style="margin-left: 0.75rem;">Letzte Fahrten</h2> %= include '_history_trains', date_format => '%d.%m.%Y', journeys => [journeys->get(uid => $user->{id}, limit => 5, with_datetime => 1)]; % } @@ -89,9 +104,11 @@ <div class="row"> <div class="col s12"> <p> - Travelynx erlaubt das Einchecken in Züge im Netz der Deutschen - Bahn. So können die eigenen Fahrten später inklusive Echtzeitdaten - und eingetragenen Servicemeldungen nachvollzogen und brennende + Travelynx erlaubt das Einchecken in Verkehrsmittel (Busse, + Bahnen, Züge) unter anderem in Deutschland, Österreich, der + Schweiz, Luxemburg, Irland, Dänemark und Teilen der USA. So + können die eigenen Fahrten später inklusive Echtzeitdaten und + eingetragenen Servicemeldungen nachvollzogen und brennende Fragen wie „Wie viele Stunden war ich letzten Monat unterwegs?“ beantwortet werden. </p> @@ -109,7 +126,7 @@ <li>Statistiken über Reisezeiten und Verspätungen</li> <li>Unterstützung beim Ausfüllen von Fahrgastrechteformularen</li> <li>Optional: Öffentlicher Reisestatus und öffentliche Angaben zu vergangenen Fahrten</li> - <li>Optional: Verknüpfung mit Träwelling</li> + <!-- <li>Optional: Verknüpfung mit Träwelling</li> --> </ul> </p> <p> diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep index e74a731..e8ce9f7 100644 --- a/templates/layouts/default.html.ep +++ b/templates/layouts/default.html.ep @@ -13,7 +13,7 @@ % while (my ($key, $value) = each %{stash('opengraph') // {}}) { <meta property="og:<%= $key %>" content="<%= $value %>"> % } - % my $av = 'v82'; # asset version + % my $av = 'v87'; # asset version <link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-16x16.png" sizes="16x16"> <link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-32x32.png" sizes="32x32"> <link rel="icon" type="image/png" href="/static/<%= $av %>/icons/icon-96x96.png" sizes="96x96"> diff --git a/templates/legend.html.ep b/templates/legend.html.ep index 73fded9..3dc113a 100644 --- a/templates/legend.html.ep +++ b/templates/legend.html.ep @@ -15,11 +15,11 @@ </tr> <tr> <td><i class="material-icons">train</i></td> - <td>Backend: DB IRIS. Bevorzugte Datenquelle für (mindestens teilweise) innerdeutsche Zugfahrten.</td> + <td>Backend: Deutsche Bahn (bahn.de oder IRIS-TTS).</td> </tr> <tr> <td><i class="material-icons">directions</i></td> - <td>Backend: DB HAFAS. Bevorzugte Datenquelle für Nahverkehr und vollständig außerdeutsche Zugfahrten. Weniger detailliert als IRIS.</td> + <td>Backend: HAFAS.</td> </tr> </tbody> </table> diff --git a/templates/profile.html.ep b/templates/profile.html.ep index 6f78ea0..a2c965c 100644 --- a/templates/profile.html.ep +++ b/templates/profile.html.ep @@ -79,7 +79,7 @@ </div> <div class="row"> <div class="col s12 publicstatuscol" data-user="<%= $name %>" data-profile="1"> - %= include '_public_status_card', name => $name, privacy => $privacy, journey => $journey, from_profile => 1 + %= include '_public_status_card', name => $name, privacy => $privacy, journey => $journey, from_profile => 1, station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') </div> </div> % if ($journeys and @{$journeys}) { diff --git a/templates/select_backend.html.ep b/templates/select_backend.html.ep index db1674e..e54bcfd 100644 --- a/templates/select_backend.html.ep +++ b/templates/select_backend.html.ep @@ -3,6 +3,7 @@ <h2>Backend auswählen</h2> <p style="text-align: justify;"> Das ausgewählte Backend bestimmt die Datenquelle für Fahrten in travelynx. + <a href="#help">Details</a>. </p> </div> </div> @@ -14,6 +15,11 @@ <div class="row"> <div class="col s12"> <h3>Vorschläge</h3> + <p style="text-align: justify;"> + Anhand der Zielstation der letzten Fahrt und den + empfohlenen Nutzungsbereichen der verfügbaren Backends + (soweit bekannt). + </p> </div> </div> % for my $backend (@{ stash('suggestions') // [] }) { @@ -22,27 +28,57 @@ % } <div class="row"> <div class="col s12"> - <h3>Alle Backends</h3> + <h3>Empfohlen</h3> + <p style="text-align: justify;"> + <strong>bahn.de</strong> für Regional- und Fernverkehr in Deutschland. + <strong>ÖBB</strong> für Nah-, Regional- und Fernverkehr in Österreich sowie Regional- und Fernverkehr in der EU. + </p> </div> </div> - % for my $backend (@{ stash('backends') // [] }) { + % for my $backend (grep { $_->{recommended} } @{ stash('backends') // [] }) { + %= include '_backend_line', user => $user, backend => $backend + % } + <div class="row"> + <div class="col s12"> + <h3>Verbünde</h3> + <p style="text-align: justify;"> + Diese Backends sind meist die beste Wahl für + Nahverkehrsfahrten in der jeweiligen Region. + Backends außerhalb Deutschlands sind im Regelfall auch + für dortigen Regional- und Fernverkehr die beste Wahl. + </p> + </div> + </div> + % for my $backend (grep { $_->{association} } @{ stash('backends') // [] }) { + %= include '_backend_line', user => $user, backend => $backend + % } + <div class="row"> + <div class="col s12"> + <h3>Experimentell oder abgekündigt</h3> + <p style="text-align: justify;"> + Einchecken auf eigene Gefahr. + </p> + </div> + </div> + % for my $backend (grep { $_->{experimental} or $_->{legacy} } @{ stash('backends') // [] }) { %= include '_backend_line', user => $user, backend => $backend % } %= end <div class="row"> <div class="col s12"> - <h2>Details</h2> + <h2 id="help">Hilfe</h2> <p> - <strong>Deutsche Bahn</strong> ist eine gute Wahl für Nah-, Regional- und Fernverkehr in Deutschland und (teilweise) Nachbarländern. - Hier stehen zumeist brauchbare Echtzeitdaten zur Verfügung; bei Zügen sind zusätzlich Kartendaten vorhanden. - </p> + <strong>Deutsche Bahn: bahn.de</strong> ist eine gute Wahl für Fahrten des Nah-, Regional- und Fernverkehrs innerhalb Deutschlands. + Die Implementierung ist noch recht frisch, bietet jedoch prinzipiell akkurate Echtzeit- und Kartendaten sowie Wagenreihungen. <p> - <strong>Deutsche Bahn (IRIS-TTS)</strong> unterstützt ausschließlich Schienenverkehr; im Gegensatz zum HAFAS sind hier detaillierte Verspätungsgründe verfügbar. + <strong>Deutsche Bahn: IRIS-TTS</strong> liefert Echtzeitdaten (nur am Start- und Zielbahnhof), Wagenreihungen und Verspätungsmeldungen für Regional- und Fernverkehr in Deutschland. Kartendaten sind nur teilweise verfügbar. + <strong>ÖBB</strong> liefern Kartendaten und Wagenreihungen für Fernverkehr in Deutschland und Umgebung, jedoch keine Meldungen. Echtzeitdaten sind teilweise verfügbar. </p> <p> - Die restlichen Backends können sich für Fahrten in den zugehörigen Verkehrsverbünden lohnen. - Im Gegensatz zum Deutsche Bahn-HAFAS haben sie oft besser gepflegte Echtzeitdaten und liefern in vielen (aber nicht allen) Fällen auch Kartendaten für Nahverkehrsmittel wie Busse oder Stadtbahnen. + Die restlichen Backends lohnen sich für Fahrten in den zugehörigen Verkehrsverbünden bzw. Ländern. + Im Gegensatz zu bahn.de liefern sie in vielen (aber nicht allen) Fällen auch detaillierte Kartendaten für die dem Verbund zugehörigen Verkehrsmittel. In Einzelfällen (z.B. BVG) sind sogar Auslastungsdaten eingepflegt. + Bei Fahrten außerhalb von Deutschland und der Schweiz ist <strong>ÖBB</strong> zumeist die beste Wahl. </p> </div> </div> diff --git a/templates/traewelling.html.ep b/templates/traewelling.html.ep index 1e0d65d..49b5c80 100644 --- a/templates/traewelling.html.ep +++ b/templates/traewelling.html.ep @@ -41,30 +41,6 @@ </div> % } -<div class="row"> - <div class="col s12"> - <div class="card purple"> - <div class="card-content white-text"> - <span class="card-title">Eingeschränkte Synchronisierung</span> - <p> - Träwelling und travelynx setzen unterschiedliche Schwerpunkte und haben unterschiedliche Features. - Kombiniert mit der Vielzahl an möglichen Randfällen heißt das, dass die Synchronisierung nicht immer funktioniert. - Diese Einschränkung ist bekannt und wird voraussichtlich bestehen bleiben. - </p> - <p> - Bei hohen Verspätungen, Ausfällen und nachträglichen Checkin-Änderungen ist die Synchronisierung u.U. nicht möglich und muss von Hand vorgenommen werden. - travelynx-Hooks werden bei via Träwelling vorgenommenen Checkins nicht ausgelöst. - </p> - </div> - <div class="card-action"> - <a href="https://github.com/derf/travelynx/issues" class="waves-effect waves-light btn-flat white-text"> - <i class="material-icons left" aria-hidden="true">bug_report</i>Bug melden - </a> - </div> - </div> - </div> -</div> - % if ($traewelling->{token} and ($traewelling->{expired} or $traewelling->{expiring})) { <div class="row"> <div class="col s12"> diff --git a/templates/user_status.html.ep b/templates/user_status.html.ep index 45fba54..bf11004 100644 --- a/templates/user_status.html.ep +++ b/templates/user_status.html.ep @@ -1,5 +1,5 @@ <div class="row"> <div class="col s12 publicstatuscol" data-user="<%= $name %>"> - %= include '_public_status_card', name => $name, privacy => $privacy, journey => $journey + %= include '_public_status_card', name => $name, privacy => $privacy, journey => $journey, station_coordinates => stash('station_coordinates'), polyline_groups => stash('polyline_groups') </div> </div> |