summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.mailmap1
-rw-r--r--.reuse/dep552
-rw-r--r--COPYING603
-rw-r--r--Dockerfile56
-rw-r--r--LICENSES/AGPL-3.0-or-later.txt603
-rw-r--r--LICENSES/Apache-2.0.txt183
-rw-r--r--LICENSES/BSD-2-Clause.txt22
-rw-r--r--LICENSES/CC-BY-4.0.txt324
-rw-r--r--LICENSES/CC0-1.0.txt121
-rw-r--r--LICENSES/MIT.txt20
-rw-r--r--README11
-rw-r--r--README.md178
-rw-r--r--cpanfile18
-rw-r--r--cpanfile.snapshot2418
-rw-r--r--examples/db-infoscreen.service23
-rw-r--r--examples/dbf_update_zugbildungsplan9
-rw-r--r--examples/imprint.html.ep7
-rw-r--r--examples/nginx-cache.conf2
-rw-r--r--examples/nginx-site.conf32
-rw-r--r--examples/privacy.html.ep34
-rw-r--r--index.pl794
-rw-r--r--lib/DBInfoscreen.pm375
-rw-r--r--lib/DBInfoscreen/Controller/Map.pm1389
-rw-r--r--lib/DBInfoscreen/Controller/Static.pm47
-rw-r--r--lib/DBInfoscreen/Controller/Stationboard.pm3052
-rw-r--r--lib/DBInfoscreen/Controller/Wagenreihung.pm267
-rw-r--r--lib/DBInfoscreen/Helper/DBRIS.pm93
-rw-r--r--lib/DBInfoscreen/Helper/EFA.pm162
-rw-r--r--lib/DBInfoscreen/Helper/HAFAS.pm318
-rw-r--r--lib/DBInfoscreen/Helper/MOTIS.pm82
-rw-r--r--lib/DBInfoscreen/Helper/Wagonorder.pm154
-rw-r--r--lib/DBInfoscreen/I18N/en.pm84
-rw-r--r--public/collapse.js22
-rw-r--r--public/default.css446
-rw-r--r--public/favicon.icobin0 -> 2462 bytes
-rw-r--r--public/jquery-1.10.2.min.js6
-rw-r--r--public/mobile.css23
-rw-r--r--public/static/css/dark.min.css1
-rw-r--r--public/static/css/jquery-ui.min.css7
-rw-r--r--public/static/css/legacy-mobile.css27
-rw-r--r--public/static/css/legacy.css788
-rw-r--r--public/static/css/light.min.css1
-rw-r--r--public/static/css/material-icons.css36
-rw-r--r--public/static/fonts/MaterialIcons-Regular.eotbin0 -> 143258 bytes
-rw-r--r--public/static/fonts/MaterialIcons-Regular.ttfbin0 -> 128180 bytes
-rw-r--r--public/static/fonts/MaterialIcons-Regular.woffbin0 -> 57620 bytes
-rw-r--r--public/static/fonts/MaterialIcons-Regular.woff2bin0 -> 44300 bytes
-rw-r--r--public/static/icons/bahn-expert.svg5
-rw-r--r--public/static/icons/dbf.svg90
-rw-r--r--public/static/icons/icon-120x120.pngbin0 -> 2837 bytes
-rw-r--r--public/static/icons/icon-128x128.pngbin0 -> 3069 bytes
-rw-r--r--public/static/icons/icon-144x144.pngbin0 -> 3435 bytes
-rw-r--r--public/static/icons/icon-152x152.pngbin0 -> 3527 bytes
-rw-r--r--public/static/icons/icon-167x167.pngbin0 -> 4145 bytes
-rw-r--r--public/static/icons/icon-16x16.pngbin0 -> 652 bytes
-rw-r--r--public/static/icons/icon-180x180.pngbin0 -> 4430 bytes
-rw-r--r--public/static/icons/icon-192x192.pngbin0 -> 4475 bytes
-rw-r--r--public/static/icons/icon-256x256.pngbin0 -> 5944 bytes
-rw-r--r--public/static/icons/icon-32x32.pngbin0 -> 1032 bytes
-rw-r--r--public/static/icons/icon-512x512.pngbin0 -> 12487 bytes
-rw-r--r--public/static/icons/icon-96x96.pngbin0 -> 2374 bytes
-rw-r--r--public/static/js/collapse.js250
-rw-r--r--public/static/js/dbf.min.js1
-rw-r--r--public/static/js/geostop.js99
-rw-r--r--public/static/js/geostop.min.js1
-rw-r--r--public/static/js/jquery-3.4.1.min.js2
-rw-r--r--public/static/js/jquery-ui.min.js6
-rw-r--r--public/static/js/map-refresh.js95
-rw-r--r--public/static/js/map-refresh.min.js1
-rw-r--r--public/static/js/marquee.js (renamed from public/marquee.js)5
-rw-r--r--public/static/js/marquee.min.js1
-rw-r--r--public/static/leaflet/images/layers-2x.pngbin0 -> 1259 bytes
-rw-r--r--public/static/leaflet/images/layers.pngbin0 -> 696 bytes
-rw-r--r--public/static/leaflet/images/marker-icon-2x-gold.pngbin0 -> 4274 bytes
-rw-r--r--public/static/leaflet/images/marker-icon-2x-green.pngbin0 -> 4203 bytes
-rw-r--r--public/static/leaflet/images/marker-icon-2x.pngbin0 -> 2464 bytes
-rw-r--r--public/static/leaflet/images/marker-icon.pngbin0 -> 1466 bytes
-rw-r--r--public/static/leaflet/images/marker-shadow.pngbin0 -> 618 bytes
-rw-r--r--public/static/leaflet/leaflet.css640
-rw-r--r--public/static/leaflet/leaflet.js5
l---------public/static/v1091
l---------public/static/v1101
-rw-r--r--sass/app.scss1153
-rw-r--r--sass/dark.scss57
-rw-r--r--sass/light.scss57
-rwxr-xr-xscripts/asset-rebuild14
-rwxr-xr-xscripts/asset-release18
-rw-r--r--share/dbdb_wagen.json1
-rw-r--r--t/01-basic.t9
-rw-r--r--t/22-json.t46
-rw-r--r--t/22-marudor.t50
-rw-r--r--t/31-clean-ris.t24
-rw-r--r--templates/_error.html.ep5
-rw-r--r--templates/_map_infobox.html.ep73
-rw-r--r--templates/_train_attr.html.ep18
-rw-r--r--templates/_train_details.html.ep415
-rw-r--r--templates/_wagon.html.ep97
-rw-r--r--templates/about.html.ep58
-rw-r--r--templates/app.html.ep214
-rw-r--r--templates/clean.html.ep240
-rw-r--r--templates/coverage_map.html.ep22
-rw-r--r--templates/exception.html.ep17
-rw-r--r--templates/geostop.html.ep5
-rw-r--r--templates/infoscreen.html.ep158
-rw-r--r--templates/landingpage.html.ep32
-rw-r--r--templates/layouts/app.html.ep320
-rw-r--r--templates/layouts/default.html.ep193
-rw-r--r--templates/layouts/legacy.html.ep66
-rw-r--r--templates/multi.html.ep6
-rw-r--r--templates/not_found.html.ep15
-rw-r--r--templates/route_map.html.ep98
-rw-r--r--templates/select_backend.html.ep46
-rw-r--r--templates/single.html.ep8
-rw-r--r--templates/train_details.html.ep5
-rw-r--r--templates/wagen.html.ep75
-rw-r--r--templates/wagenreihung.html.ep57
-rw-r--r--templates/zugbildung_db.html.ep26
117 files changed, 15972 insertions, 1820 deletions
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..c69b0d4
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1 @@
+Birte Kristina Friesel <derf@finalrewind.org>
diff --git a/.reuse/dep5 b/.reuse/dep5
new file mode 100644
index 0000000..46e041a
--- /dev/null
+++ b/.reuse/dep5
@@ -0,0 +1,52 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files: examples/*
+Copyright: 2020 Birte Kristina Friesel
+License: CC0-1.0
+
+Files: public/static/css/dark.min.css public/static/css/light.min.css
+Copyright: 2020 Birte Kristina Friesel
+License: BSD-2-Clause
+
+Files: public/static/css/material-icons.css public/static/fonts/MaterialIcons-*
+Copyright: 2014-2019 Materialize
+License: Apache-2.0
+
+Files: public/static/icons/* public/favicon.ico
+Copyright: 2014-2019 Materialize
+ 2020 Birte Kristina Friesel
+License: Apache-2.0
+
+Files: public/static/js/autocomplete.*
+Copyright: 2020 DB Station&Service AG, Europaplatz 1, 10557 Berlin
+ 2020 Birte Kristina Friesel
+License: CC-BY-4.0
+
+Files: public/static/js/dbf.min.js public/static/js/geolocation.min.js public/static/js/map-refresh.min.js
+Copyright: 2020 Birte Kristina Friesel
+License: AGPL-3.0-or-later
+
+Files: public/static/js/jquery* public/static/css/jquery-ui.min.css
+Copyright: 2019 jQuery Foundation and other contributors
+License: MIT
+
+Files: public/static/js/marquee*
+Copyright: 2013 Remy Sharp
+License: MIT
+
+Files: public/static/leaflet/*
+Copyright: 2010-2019 Vladimir Agafonkin
+ 2010-2011 CloudMade
+License: BSD-2-Clause
+
+Files: README.md .gitignore cpanfile cpanfile.snapshot
+Copyright: 2020 Birte Kristina Friesel
+License: CC0-1.0
+
+Files: share/dbdb_wagen.json
+Copyright: 2020 Birte Kristina Friesel
+License: CC0-1.0
+
+Files: templates/*
+Copyright: 2020 Birte Kristina Friesel
+License: BSD-2-Clause
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..3e275d8
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,603 @@
+GNU AFFERO GENERAL PUBLIC LICENSE
+Version 3, 19 November 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+ Preamble
+
+The GNU Affero General Public License is a free, copyleft license for software
+and other kinds of works, specifically designed to ensure cooperation with
+the community in the case of network server software.
+
+The licenses for most software and other practical works are designed to take
+away your freedom to share and change the works. By contrast, our General
+Public Licenses are intended to guarantee your freedom to share and change
+all versions of a program--to make sure it remains free software for all its
+users.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for them if you wish), that
+you receive source code or can get it if you want it, that you can change
+the software or use pieces of it in new free programs, and that you know you
+can do these things.
+
+Developers that use our General Public Licenses protect your rights with two
+steps: (1) assert copyright on the software, and (2) offer you this License
+which gives you legal permission to copy, distribute and/or modify the software.
+
+A secondary benefit of defending all users' freedom is that improvements made
+in alternate versions of the program, if they receive widespread use, become
+available for other developers to incorporate. Many developers of free software
+are heartened and encouraged by the resulting cooperation. However, in the
+case of software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and letting
+the public access it on a server without ever releasing its source code to
+the public.
+
+The GNU Affero General Public License is designed specifically to ensure that,
+in such cases, the modified source code becomes available to the community.
+It requires the operator of a network server to provide the source code of
+the modified version running there to the users of that server. Therefore,
+public use of a modified version, on a publicly accessible server, gives the
+public access to the source code of the modified version.
+
+An older license, called the Affero General Public License and published by
+Affero, was designed to accomplish similar goals. This is a different license,
+not a version of the Affero GPL, but Affero has released a new version of
+the Affero GPL which permits relicensing under this license.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+ TERMS AND CONDITIONS
+
+0. Definitions.
+
+"This License" refers to version 3 of the GNU Affero General Public License.
+
+"Copyright" also means copyright-like laws that apply to other kinds of works,
+such as semiconductor masks.
+
+"The Program" refers to any copyrightable work licensed under this License.
+Each licensee is addressed as "you". "Licensees" and "recipients" may be
+individuals or organizations.
+
+To "modify" a work means to copy from or adapt all or part of the work in
+a fashion requiring copyright permission, other than the making of an exact
+copy. The resulting work is called a "modified version" of the earlier work
+or a work "based on" the earlier work.
+
+A "covered work" means either the unmodified Program or a work based on the
+Program.
+
+To "propagate" a work means to do anything with it that, without permission,
+would make you directly or secondarily liable for infringement under applicable
+copyright law, except executing it on a computer or modifying a private copy.
+Propagation includes copying, distribution (with or without modification),
+making available to the public, and in some countries other activities as
+well.
+
+To "convey" a work means any kind of propagation that enables other parties
+to make or receive copies. Mere interaction with a user through a computer
+network, with no transfer of a copy, is not conveying.
+
+An interactive user interface displays "Appropriate Legal Notices" to the
+extent that it includes a convenient and prominently visible feature that
+(1) displays an appropriate copyright notice, and (2) tells the user that
+there is no warranty for the work (except to the extent that warranties are
+provided), that licensees may convey the work under this License, and how
+to view a copy of this License. If the interface presents a list of user
+commands or options, such as a menu, a prominent item in the list meets this
+criterion.
+
+1. Source Code.
+The "source code" for a work means the preferred form of the work for making
+modifications to it. "Object code" means any non-source form of a work.
+
+A "Standard Interface" means an interface that either is an official standard
+defined by a recognized standards body, or, in the case of interfaces specified
+for a particular programming language, one that is widely used among developers
+working in that language.
+
+The "System Libraries" of an executable work include anything, other than
+the work as a whole, that (a) is included in the normal form of packaging
+a Major Component, but which is not part of that Major Component, and (b)
+serves only to enable use of the work with that Major Component, or to implement
+a Standard Interface for which an implementation is available to the public
+in source code form. A "Major Component", in this context, means a major
+essential component (kernel, window system, and so on) of the specific operating
+system (if any) on which the executable work runs, or a compiler used to produce
+the work, or an object code interpreter used to run it.
+
+The "Corresponding Source" for a work in object code form means all the source
+code needed to generate, install, and (for an executable work) run the object
+code and to modify the work, including scripts to control those activities.
+However, it does not include the work's System Libraries, or general-purpose
+tools or generally available free programs which are used unmodified in performing
+those activities but which are not part of the work. For example, Corresponding
+Source includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically linked
+subprograms that the work is specifically designed to require, such as by
+intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can regenerate
+automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same work.
+
+2. Basic Permissions.
+All rights granted under this License are granted for the term of copyright
+on the Program, and are irrevocable provided the stated conditions are met.
+This License explicitly affirms your unlimited permission to run the unmodified
+Program. The output from running a covered work is covered by this License
+only if the output, given its content, constitutes a covered work. This License
+acknowledges your rights of fair use or other equivalent, as provided by copyright
+law.
+
+You may make, run and propagate covered works that you do not convey, without
+conditions so long as your license otherwise remains in force. You may convey
+covered works to others for the sole purpose of having them make modifications
+exclusively for you, or provide you with facilities for running those works,
+provided that you comply with the terms of this License in conveying all material
+for which you do not control copyright. Those thus making or running the
+covered works for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of your copyrighted
+material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under the conditions
+stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
+
+3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+No covered work shall be deemed part of an effective technological measure
+under any applicable law fulfilling obligations under article 11 of the WIPO
+copyright treaty adopted on 20 December 1996, or similar laws prohibiting
+or restricting circumvention of such measures.
+
+When you convey a covered work, you waive any legal power to forbid circumvention
+of technological measures to the extent such circumvention is effected by
+exercising rights under this License with respect to the covered work, and
+you disclaim any intention to limit operation or modification of the work
+as a means of enforcing, against the work's users, your or third parties'
+legal rights to forbid circumvention of technological measures.
+
+4. Conveying Verbatim Copies.
+You may convey verbatim copies of the Program's source code as you receive
+it, in any medium, provided that you conspicuously and appropriately publish
+on each copy an appropriate copyright notice; keep intact all notices stating
+that this License and any non-permissive terms added in accord with section
+7 apply to the code; keep intact all notices of the absence of any warranty;
+and give all recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey, and you
+may offer support or warranty protection for a fee.
+
+5. Conveying Modified Source Versions.
+You may convey a work based on the Program, or the modifications to produce
+it from the Program, in the form of source code under the terms of section
+4, provided that you also meet all of these conditions:
+
+a) The work must carry prominent notices stating that you modified it, and
+giving a relevant date.
+
+b) The work must carry prominent notices stating that it is released under
+this License and any conditions added under section 7. This requirement modifies
+the requirement in section 4 to "keep intact all notices".
+
+c) You must license the entire work, as a whole, under this License to anyone
+who comes into possession of a copy. This License will therefore apply, along
+with any applicable section 7 additional terms, to the whole of the work,
+and all its parts, regardless of how they are packaged. This License gives
+no permission to license the work in any other way, but it does not invalidate
+such permission if you have separately received it.
+
+d) If the work has interactive user interfaces, each must display Appropriate
+Legal Notices; however, if the Program has interactive interfaces that do
+not display Appropriate Legal Notices, your work need not make them do so.
+
+A compilation of a covered work with other separate and independent works,
+which are not by their nature extensions of the covered work, and which are
+not combined with it such as to form a larger program, in or on a volume of
+a storage or distribution medium, is called an "aggregate" if the compilation
+and its resulting copyright are not used to limit the access or legal rights
+of the compilation's users beyond what the individual works permit. Inclusion
+of a covered work in an aggregate does not cause this License to apply to
+the other parts of the aggregate.
+
+6. Conveying Non-Source Forms.
+You may convey a covered work in object code form under the terms of sections
+4 and 5, provided that you also convey the machine-readable Corresponding
+Source under the terms of this License, in one of these ways:
+
+a) Convey the object code in, or embodied in, a physical product (including
+a physical distribution medium), accompanied by the Corresponding Source fixed
+on a durable physical medium customarily used for software interchange.
+
+b) Convey the object code in, or embodied in, a physical product (including
+a physical distribution medium), accompanied by a written offer, valid for
+at least three years and valid for as long as you offer spare parts or customer
+support for that product model, to give anyone who possesses the object code
+either (1) a copy of the Corresponding Source for all the software in the
+product that is covered by this License, on a durable physical medium customarily
+used for software interchange, for a price no more than your reasonable cost
+of physically performing this conveying of source, or (2) access to copy the
+Corresponding Source from a network server at no charge.
+
+c) Convey individual copies of the object code with a copy of the written
+offer to provide the Corresponding Source. This alternative is allowed only
+occasionally and noncommercially, and only if you received the object code
+with such an offer, in accord with subsection 6b.
+
+d) Convey the object code by offering access from a designated place (gratis
+or for a charge), and offer equivalent access to the Corresponding Source
+in the same way through the same place at no further charge. You need not
+require recipients to copy the Corresponding Source along with the object
+code. If the place to copy the object code is a network server, the Corresponding
+Source may be on a different server (operated by you or a third party) that
+supports equivalent copying facilities, provided you maintain clear directions
+next to the object code saying where to find the Corresponding Source. Regardless
+of what server hosts the Corresponding Source, you remain obligated to ensure
+that it is available for as long as needed to satisfy these requirements.
+
+e) Convey the object code using peer-to-peer transmission, provided you inform
+other peers where the object code and Corresponding Source of the work are
+being offered to the general public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded from
+the Corresponding Source as a System Library, need not be included in conveying
+the object code work.
+
+A "User Product" is either (1) a "consumer product", which means any tangible
+personal property which is normally used for personal, family, or household
+purposes, or (2) anything designed or sold for incorporation into a dwelling.
+In determining whether a product is a consumer product, doubtful cases shall
+be resolved in favor of coverage. For a particular product received by a
+particular user, "normally used" refers to a typical or common use of that
+class of product, regardless of the status of the particular user or of the
+way in which the particular user actually uses, or expects or is expected
+to use, the product. A product is a consumer product regardless of whether
+the product has substantial commercial, industrial or non-consumer uses, unless
+such uses represent the only significant mode of use of the product.
+
+"Installation Information" for a User Product means any methods, procedures,
+authorization keys, or other information required to install and execute modified
+versions of a covered work in that User Product from a modified version of
+its Corresponding Source. The information must suffice to ensure that the
+continued functioning of the modified object code is in no case prevented
+or interfered with solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or specifically
+for use in, a User Product, and the conveying occurs as part of a transaction
+in which the right of possession and use of the User Product is transferred
+to the recipient in perpetuity or for a fixed term (regardless of how the
+transaction is characterized), the Corresponding Source conveyed under this
+section must be accompanied by the Installation Information. But this requirement
+does not apply if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has been installed
+in ROM).
+
+The requirement to provide Installation Information does not include a requirement
+to continue to provide support service, warranty, or updates for a work that
+has been modified or installed by the recipient, or for the User Product in
+which it has been modified or installed. Access to a network may be denied
+when the modification itself materially and adversely affects the operation
+of the network or violates the rules and protocols for communication across
+the network.
+
+Corresponding Source conveyed, and Installation Information provided, in accord
+with this section must be in a format that is publicly documented (and with
+an implementation available to the public in source code form), and must require
+no special password or key for unpacking, reading or copying.
+
+7. Additional Terms.
+"Additional permissions" are terms that supplement the terms of this License
+by making exceptions from one or more of its conditions. Additional permissions
+that are applicable to the entire Program shall be treated as though they
+were included in this License, to the extent that they are valid under applicable
+law. If additional permissions apply only to part of the Program, that part
+may be used separately under those permissions, but the entire Program remains
+governed by this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option remove any
+additional permissions from that copy, or from any part of it. (Additional
+permissions may be written to require their own removal in certain cases when
+you modify the work.) You may place additional permissions on material, added
+by you to a covered work, for which you have or can give appropriate copyright
+permission.
+
+Notwithstanding any other provision of this License, for material you add
+to a covered work, you may (if authorized by the copyright holders of that
+material) supplement the terms of this License with terms:
+
+a) Disclaiming warranty or limiting liability differently from the terms of
+sections 15 and 16 of this License; or
+
+b) Requiring preservation of specified reasonable legal notices or author
+attributions in that material or in the Appropriate Legal Notices displayed
+by works containing it; or
+
+c) Prohibiting misrepresentation of the origin of that material, or requiring
+that modified versions of such material be marked in reasonable ways as different
+from the original version; or
+
+d) Limiting the use for publicity purposes of names of licensors or authors
+of the material; or
+
+e) Declining to grant rights under trademark law for use of some trade names,
+trademarks, or service marks; or
+
+f) Requiring indemnification of licensors and authors of that material by
+anyone who conveys the material (or modified versions of it) with contractual
+assumptions of liability to the recipient, for any liability that these contractual
+assumptions directly impose on those licensors and authors.
+
+All other non-permissive additional terms are considered "further restrictions"
+within the meaning of section 10. If the Program as you received it, or any
+part of it, contains a notice stating that it is governed by this License
+along with a term that is a further restriction, you may remove that term.
+If a license document contains a further restriction but permits relicensing
+or conveying under this License, you may add to a covered work material governed
+by the terms of that license document, provided that the further restriction
+does not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you must place,
+in the relevant source files, a statement of the additional terms that apply
+to those files, or a notice indicating where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the form
+of a separately written license, or stated as exceptions; the above requirements
+apply either way.
+
+8. Termination.
+
+You may not propagate or modify a covered work except as expressly provided
+under this License. Any attempt otherwise to propagate or modify it is void,
+and will automatically terminate your rights under this License (including
+any patent licenses granted under the third paragraph of section 11).
+
+However, if you cease all violation of this License, then your license from
+a particular copyright holder is reinstated (a) provisionally, unless and
+until the copyright holder explicitly and finally terminates your license,
+and (b) permanently, if the copyright holder fails to notify you of the violation
+by some reasonable means prior to 60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is reinstated permanently
+if the copyright holder notifies you of the violation by some reasonable means,
+this is the first time you have received notice of violation of this License
+(for any work) from that copyright holder, and you cure the violation prior
+to 30 days after your receipt of the notice.
+
+Termination of your rights under this section does not terminate the licenses
+of parties who have received copies or rights from you under this License.
+If your rights have been terminated and not permanently reinstated, you do
+not qualify to receive new licenses for the same material under section 10.
+
+9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or run a copy
+of the Program. Ancillary propagation of a covered work occurring solely
+as a consequence of using peer-to-peer transmission to receive a copy likewise
+does not require acceptance. However, nothing other than this License grants
+you permission to propagate or modify any covered work. These actions infringe
+copyright if you do not accept this License. Therefore, by modifying or propagating
+a covered work, you indicate your acceptance of this License to do so.
+
+10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically receives
+a license from the original licensors, to run, modify and propagate that work,
+subject to this License. You are not responsible for enforcing compliance
+by third parties with this License.
+
+An "entity transaction" is a transaction transferring control of an organization,
+or substantially all assets of one, or subdividing an organization, or merging
+organizations. If propagation of a covered work results from an entity transaction,
+each party to that transaction who receives a copy of the work also receives
+whatever licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the Corresponding
+Source of the work from the predecessor in interest, if the predecessor has
+it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the rights
+granted or affirmed under this License. For example, you may not impose a
+license fee, royalty, or other charge for exercise of rights granted under
+this License, and you may not initiate litigation (including a cross-claim
+or counterclaim in a lawsuit) alleging that any patent claim is infringed
+by making, using, selling, offering for sale, or importing the Program or
+any portion of it.
+
+11. Patents.
+
+A "contributor" is a copyright holder who authorizes use under this License
+of the Program or a work on which the Program is based. The work thus licensed
+is called the contributor's "contributor version".
+
+A contributor's "essential patent claims" are all patent claims owned or controlled
+by the contributor, whether already acquired or hereafter acquired, that would
+be infringed by some manner, permitted by this License, of making, using,
+or selling its contributor version, but do not include claims that would be
+infringed only as a consequence of further modification of the contributor
+version. For purposes of this definition, "control" includes the right to
+grant patent sublicenses in a manner consistent with the requirements of this
+License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free patent
+license under the contributor's essential patent claims, to make, use, sell,
+offer for sale, import and otherwise run, modify and propagate the contents
+of its contributor version.
+
+In the following three paragraphs, a "patent license" is any express agreement
+or commitment, however denominated, not to enforce a patent (such as an express
+permission to practice a patent or covenant not to sue for patent infringement).
+To "grant" such a patent license to a party means to make such an agreement
+or commitment not to enforce a patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license, and the
+Corresponding Source of the work is not available for anyone to copy, free
+of charge and under the terms of this License, through a publicly available
+network server or other readily accessible means, then you must either (1)
+cause the Corresponding Source to be so available, or (2) arrange to deprive
+yourself of the benefit of the patent license for this particular work, or
+(3) arrange, in a manner consistent with the requirements of this License,
+to extend the patent
+license to downstream recipients. "Knowingly relying" means you have actual
+knowledge that, but for the patent license, your conveying the covered work
+in a country, or your recipient's use of the covered work in a country, would
+infringe one or more identifiable patents in that country that you have reason
+to believe are valid.
+
+If, pursuant to or in connection with a single transaction or arrangement,
+you convey, or propagate by procuring conveyance of, a covered work, and grant
+a patent license to some of the parties receiving the covered work authorizing
+them to use, propagate, modify or convey a specific copy of the covered work,
+then the patent license you grant is automatically extended to all recipients
+of the covered work and works based on it.
+
+A patent license is "discriminatory" if it does not include within the scope
+of its coverage, prohibits the exercise of, or is conditioned on the non-exercise
+of one or more of the rights that are specifically granted under this License.
+You may not convey a covered work if you are a party to an arrangement with
+a third party that is in the business of distributing software, under which
+you make payment to the third party based on the extent of your activity of
+conveying the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory patent
+license (a) in connection with copies of the covered work conveyed by you
+(or copies made from those copies), or (b) primarily for and in connection
+with specific products or compilations that contain the covered work, unless
+you entered into that arrangement, or that patent license was granted, prior
+to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting any implied
+license or other defenses to infringement that may otherwise be available
+to you under applicable patent law.
+
+12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or otherwise)
+that contradict the conditions of this License, they do not excuse you from
+the conditions of this License. If you cannot convey a covered work so as
+to satisfy simultaneously your obligations under this License and any other
+pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey the
+Program, the only way you could satisfy both those terms and this License
+would be to refrain entirely from conveying the Program.
+
+13. Remote Network Interaction; Use with the GNU General Public License.
+
+Notwithstanding any other provision of this License, if you modify the Program,
+your modified version must prominently offer all users interacting with it
+remotely through a computer network (if your version supports such interaction)
+an opportunity to receive the Corresponding Source of your version by providing
+access to the Corresponding Source from a network server at no charge, through
+some standard or customary means of facilitating copying of software. This
+Corresponding Source shall include the Corresponding Source for any work covered
+by version 3 of the GNU General Public License that is incorporated pursuant
+to the following paragraph.
+
+Notwithstanding any other provision of this License, you have permission to
+link or combine any covered work with a work licensed under version 3 of the
+GNU General Public License into a single combined work, and to convey the
+resulting work. The terms of this License will continue to apply to the part
+which is the covered work, but the work with which it is combined will remain
+governed by version 3 of the GNU General Public License.
+
+14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions of the
+GNU Affero General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to address
+new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies
+that a certain numbered version of the GNU Affero General Public License "or
+any later version" applies to it, you have the option of following the terms
+and conditions either of that numbered version or of any later version published
+by the Free Software Foundation. If the Program does not specify a version
+number of the GNU Affero General Public License, you may choose any version
+ever published by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions of
+the GNU Affero General Public License can be used, that proxy's public statement
+of acceptance of a version permanently authorizes you to choose that version
+for the Program.
+
+Later license versions may give you additional or different permissions.
+However, no additional obligations are imposed on any author or copyright
+holder as a result of your choosing to follow a later version.
+
+15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
+LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
+AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR
+OR CORRECTION.
+
+16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
+ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM
+AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
+INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO
+USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
+INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
+PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
+PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided above cannot
+be given local legal effect according to their terms, reviewing courts shall
+apply local law that most closely approximates an absolute waiver of all civil
+liability in connection with the Program, unless a warranty or assumption
+of liability accompanies a copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach
+them to the start of each source file to most effectively state the exclusion
+of warranty; and each file should have at least the "copyright" line and a
+pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+This program is free software: you can redistribute it and/or modify it under
+the terms of the GNU Affero General Public License as published by the Free
+Software Foundation, either version 3 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+details.
+
+You should have received a copy of the GNU Affero General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If your software can interact with users remotely through a computer network,
+you should also make sure that it provides a way for users to get its source.
+For example, if your program is a web application, its interface could display
+a "Source" link that leads users to an archive of the code. There are many
+ways you could offer source, and different solutions will be better for different
+programs; see section 13 for the specific requirements.
+
+You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary. For
+more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..210893b
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,56 @@
+# Copyright (C) 2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: CC0-1.0
+
+# docker build -t db-fakedisplay:latest --build-arg=dbf_version=$(git describe --dirty) .
+
+FROM debian:buster-slim as files
+
+ARG dbf_version=git
+
+COPY index.pl /app/
+COPY lib/ /app/lib/
+COPY public/ /app/public/
+COPY templates/ /app/templates/
+COPY share/ /app/share/
+
+WORKDIR /app
+
+RUN ln -sf ../ext-templates/imprint.html.ep templates/imprint.html.ep \
+ && ln -sf ../ext-templates/privacy.html.ep templates/privacy.html.ep
+
+RUN sed -i "s/version *=> *\$ENV{DBFAKEDISPLAY_VERSION}/version => '${dbf_version}'/" lib/DBInfoscreen.pm
+
+FROM perl:5.40-slim
+
+ARG DEBIAN_FRONTEND=noninteractive
+ARG APT_LISTCHANGES_FRONTEND=none
+
+COPY cpanfile* /app/
+WORKDIR /app
+
+RUN apt-get update \
+ && apt-get -y --no-install-recommends install \
+ ca-certificates \
+ curl \
+ gcc \
+ libc6-dev \
+ libdb5.3 \
+ libdb5.3-dev \
+ libssl3 \
+ libssl-dev \
+ libxml2 \
+ libxml2-dev \
+ make \
+ zlib1g-dev \
+ && cpanm -n --no-man-pages --installdeps . \
+ && rm -rf ~/.cpanm \
+ && apt-get -y purge curl gcc libc6-dev libdb5.3-dev libssl-dev libxml2-dev make zlib1g-dev \
+ && apt-get -y autoremove \
+ && rm -rf /var/cache/apt/* /var/lib/apt/lists/*
+
+COPY --from=files /app/ /app/
+
+EXPOSE 8092
+
+CMD ["hypnotoad", "-f", "index.pl"]
diff --git a/LICENSES/AGPL-3.0-or-later.txt b/LICENSES/AGPL-3.0-or-later.txt
new file mode 100644
index 0000000..3e275d8
--- /dev/null
+++ b/LICENSES/AGPL-3.0-or-later.txt
@@ -0,0 +1,603 @@
+GNU AFFERO GENERAL PUBLIC LICENSE
+Version 3, 19 November 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+ Preamble
+
+The GNU Affero General Public License is a free, copyleft license for software
+and other kinds of works, specifically designed to ensure cooperation with
+the community in the case of network server software.
+
+The licenses for most software and other practical works are designed to take
+away your freedom to share and change the works. By contrast, our General
+Public Licenses are intended to guarantee your freedom to share and change
+all versions of a program--to make sure it remains free software for all its
+users.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for them if you wish), that
+you receive source code or can get it if you want it, that you can change
+the software or use pieces of it in new free programs, and that you know you
+can do these things.
+
+Developers that use our General Public Licenses protect your rights with two
+steps: (1) assert copyright on the software, and (2) offer you this License
+which gives you legal permission to copy, distribute and/or modify the software.
+
+A secondary benefit of defending all users' freedom is that improvements made
+in alternate versions of the program, if they receive widespread use, become
+available for other developers to incorporate. Many developers of free software
+are heartened and encouraged by the resulting cooperation. However, in the
+case of software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and letting
+the public access it on a server without ever releasing its source code to
+the public.
+
+The GNU Affero General Public License is designed specifically to ensure that,
+in such cases, the modified source code becomes available to the community.
+It requires the operator of a network server to provide the source code of
+the modified version running there to the users of that server. Therefore,
+public use of a modified version, on a publicly accessible server, gives the
+public access to the source code of the modified version.
+
+An older license, called the Affero General Public License and published by
+Affero, was designed to accomplish similar goals. This is a different license,
+not a version of the Affero GPL, but Affero has released a new version of
+the Affero GPL which permits relicensing under this license.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+ TERMS AND CONDITIONS
+
+0. Definitions.
+
+"This License" refers to version 3 of the GNU Affero General Public License.
+
+"Copyright" also means copyright-like laws that apply to other kinds of works,
+such as semiconductor masks.
+
+"The Program" refers to any copyrightable work licensed under this License.
+Each licensee is addressed as "you". "Licensees" and "recipients" may be
+individuals or organizations.
+
+To "modify" a work means to copy from or adapt all or part of the work in
+a fashion requiring copyright permission, other than the making of an exact
+copy. The resulting work is called a "modified version" of the earlier work
+or a work "based on" the earlier work.
+
+A "covered work" means either the unmodified Program or a work based on the
+Program.
+
+To "propagate" a work means to do anything with it that, without permission,
+would make you directly or secondarily liable for infringement under applicable
+copyright law, except executing it on a computer or modifying a private copy.
+Propagation includes copying, distribution (with or without modification),
+making available to the public, and in some countries other activities as
+well.
+
+To "convey" a work means any kind of propagation that enables other parties
+to make or receive copies. Mere interaction with a user through a computer
+network, with no transfer of a copy, is not conveying.
+
+An interactive user interface displays "Appropriate Legal Notices" to the
+extent that it includes a convenient and prominently visible feature that
+(1) displays an appropriate copyright notice, and (2) tells the user that
+there is no warranty for the work (except to the extent that warranties are
+provided), that licensees may convey the work under this License, and how
+to view a copy of this License. If the interface presents a list of user
+commands or options, such as a menu, a prominent item in the list meets this
+criterion.
+
+1. Source Code.
+The "source code" for a work means the preferred form of the work for making
+modifications to it. "Object code" means any non-source form of a work.
+
+A "Standard Interface" means an interface that either is an official standard
+defined by a recognized standards body, or, in the case of interfaces specified
+for a particular programming language, one that is widely used among developers
+working in that language.
+
+The "System Libraries" of an executable work include anything, other than
+the work as a whole, that (a) is included in the normal form of packaging
+a Major Component, but which is not part of that Major Component, and (b)
+serves only to enable use of the work with that Major Component, or to implement
+a Standard Interface for which an implementation is available to the public
+in source code form. A "Major Component", in this context, means a major
+essential component (kernel, window system, and so on) of the specific operating
+system (if any) on which the executable work runs, or a compiler used to produce
+the work, or an object code interpreter used to run it.
+
+The "Corresponding Source" for a work in object code form means all the source
+code needed to generate, install, and (for an executable work) run the object
+code and to modify the work, including scripts to control those activities.
+However, it does not include the work's System Libraries, or general-purpose
+tools or generally available free programs which are used unmodified in performing
+those activities but which are not part of the work. For example, Corresponding
+Source includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically linked
+subprograms that the work is specifically designed to require, such as by
+intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can regenerate
+automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same work.
+
+2. Basic Permissions.
+All rights granted under this License are granted for the term of copyright
+on the Program, and are irrevocable provided the stated conditions are met.
+This License explicitly affirms your unlimited permission to run the unmodified
+Program. The output from running a covered work is covered by this License
+only if the output, given its content, constitutes a covered work. This License
+acknowledges your rights of fair use or other equivalent, as provided by copyright
+law.
+
+You may make, run and propagate covered works that you do not convey, without
+conditions so long as your license otherwise remains in force. You may convey
+covered works to others for the sole purpose of having them make modifications
+exclusively for you, or provide you with facilities for running those works,
+provided that you comply with the terms of this License in conveying all material
+for which you do not control copyright. Those thus making or running the
+covered works for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of your copyrighted
+material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under the conditions
+stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
+
+3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+No covered work shall be deemed part of an effective technological measure
+under any applicable law fulfilling obligations under article 11 of the WIPO
+copyright treaty adopted on 20 December 1996, or similar laws prohibiting
+or restricting circumvention of such measures.
+
+When you convey a covered work, you waive any legal power to forbid circumvention
+of technological measures to the extent such circumvention is effected by
+exercising rights under this License with respect to the covered work, and
+you disclaim any intention to limit operation or modification of the work
+as a means of enforcing, against the work's users, your or third parties'
+legal rights to forbid circumvention of technological measures.
+
+4. Conveying Verbatim Copies.
+You may convey verbatim copies of the Program's source code as you receive
+it, in any medium, provided that you conspicuously and appropriately publish
+on each copy an appropriate copyright notice; keep intact all notices stating
+that this License and any non-permissive terms added in accord with section
+7 apply to the code; keep intact all notices of the absence of any warranty;
+and give all recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey, and you
+may offer support or warranty protection for a fee.
+
+5. Conveying Modified Source Versions.
+You may convey a work based on the Program, or the modifications to produce
+it from the Program, in the form of source code under the terms of section
+4, provided that you also meet all of these conditions:
+
+a) The work must carry prominent notices stating that you modified it, and
+giving a relevant date.
+
+b) The work must carry prominent notices stating that it is released under
+this License and any conditions added under section 7. This requirement modifies
+the requirement in section 4 to "keep intact all notices".
+
+c) You must license the entire work, as a whole, under this License to anyone
+who comes into possession of a copy. This License will therefore apply, along
+with any applicable section 7 additional terms, to the whole of the work,
+and all its parts, regardless of how they are packaged. This License gives
+no permission to license the work in any other way, but it does not invalidate
+such permission if you have separately received it.
+
+d) If the work has interactive user interfaces, each must display Appropriate
+Legal Notices; however, if the Program has interactive interfaces that do
+not display Appropriate Legal Notices, your work need not make them do so.
+
+A compilation of a covered work with other separate and independent works,
+which are not by their nature extensions of the covered work, and which are
+not combined with it such as to form a larger program, in or on a volume of
+a storage or distribution medium, is called an "aggregate" if the compilation
+and its resulting copyright are not used to limit the access or legal rights
+of the compilation's users beyond what the individual works permit. Inclusion
+of a covered work in an aggregate does not cause this License to apply to
+the other parts of the aggregate.
+
+6. Conveying Non-Source Forms.
+You may convey a covered work in object code form under the terms of sections
+4 and 5, provided that you also convey the machine-readable Corresponding
+Source under the terms of this License, in one of these ways:
+
+a) Convey the object code in, or embodied in, a physical product (including
+a physical distribution medium), accompanied by the Corresponding Source fixed
+on a durable physical medium customarily used for software interchange.
+
+b) Convey the object code in, or embodied in, a physical product (including
+a physical distribution medium), accompanied by a written offer, valid for
+at least three years and valid for as long as you offer spare parts or customer
+support for that product model, to give anyone who possesses the object code
+either (1) a copy of the Corresponding Source for all the software in the
+product that is covered by this License, on a durable physical medium customarily
+used for software interchange, for a price no more than your reasonable cost
+of physically performing this conveying of source, or (2) access to copy the
+Corresponding Source from a network server at no charge.
+
+c) Convey individual copies of the object code with a copy of the written
+offer to provide the Corresponding Source. This alternative is allowed only
+occasionally and noncommercially, and only if you received the object code
+with such an offer, in accord with subsection 6b.
+
+d) Convey the object code by offering access from a designated place (gratis
+or for a charge), and offer equivalent access to the Corresponding Source
+in the same way through the same place at no further charge. You need not
+require recipients to copy the Corresponding Source along with the object
+code. If the place to copy the object code is a network server, the Corresponding
+Source may be on a different server (operated by you or a third party) that
+supports equivalent copying facilities, provided you maintain clear directions
+next to the object code saying where to find the Corresponding Source. Regardless
+of what server hosts the Corresponding Source, you remain obligated to ensure
+that it is available for as long as needed to satisfy these requirements.
+
+e) Convey the object code using peer-to-peer transmission, provided you inform
+other peers where the object code and Corresponding Source of the work are
+being offered to the general public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded from
+the Corresponding Source as a System Library, need not be included in conveying
+the object code work.
+
+A "User Product" is either (1) a "consumer product", which means any tangible
+personal property which is normally used for personal, family, or household
+purposes, or (2) anything designed or sold for incorporation into a dwelling.
+In determining whether a product is a consumer product, doubtful cases shall
+be resolved in favor of coverage. For a particular product received by a
+particular user, "normally used" refers to a typical or common use of that
+class of product, regardless of the status of the particular user or of the
+way in which the particular user actually uses, or expects or is expected
+to use, the product. A product is a consumer product regardless of whether
+the product has substantial commercial, industrial or non-consumer uses, unless
+such uses represent the only significant mode of use of the product.
+
+"Installation Information" for a User Product means any methods, procedures,
+authorization keys, or other information required to install and execute modified
+versions of a covered work in that User Product from a modified version of
+its Corresponding Source. The information must suffice to ensure that the
+continued functioning of the modified object code is in no case prevented
+or interfered with solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or specifically
+for use in, a User Product, and the conveying occurs as part of a transaction
+in which the right of possession and use of the User Product is transferred
+to the recipient in perpetuity or for a fixed term (regardless of how the
+transaction is characterized), the Corresponding Source conveyed under this
+section must be accompanied by the Installation Information. But this requirement
+does not apply if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has been installed
+in ROM).
+
+The requirement to provide Installation Information does not include a requirement
+to continue to provide support service, warranty, or updates for a work that
+has been modified or installed by the recipient, or for the User Product in
+which it has been modified or installed. Access to a network may be denied
+when the modification itself materially and adversely affects the operation
+of the network or violates the rules and protocols for communication across
+the network.
+
+Corresponding Source conveyed, and Installation Information provided, in accord
+with this section must be in a format that is publicly documented (and with
+an implementation available to the public in source code form), and must require
+no special password or key for unpacking, reading or copying.
+
+7. Additional Terms.
+"Additional permissions" are terms that supplement the terms of this License
+by making exceptions from one or more of its conditions. Additional permissions
+that are applicable to the entire Program shall be treated as though they
+were included in this License, to the extent that they are valid under applicable
+law. If additional permissions apply only to part of the Program, that part
+may be used separately under those permissions, but the entire Program remains
+governed by this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option remove any
+additional permissions from that copy, or from any part of it. (Additional
+permissions may be written to require their own removal in certain cases when
+you modify the work.) You may place additional permissions on material, added
+by you to a covered work, for which you have or can give appropriate copyright
+permission.
+
+Notwithstanding any other provision of this License, for material you add
+to a covered work, you may (if authorized by the copyright holders of that
+material) supplement the terms of this License with terms:
+
+a) Disclaiming warranty or limiting liability differently from the terms of
+sections 15 and 16 of this License; or
+
+b) Requiring preservation of specified reasonable legal notices or author
+attributions in that material or in the Appropriate Legal Notices displayed
+by works containing it; or
+
+c) Prohibiting misrepresentation of the origin of that material, or requiring
+that modified versions of such material be marked in reasonable ways as different
+from the original version; or
+
+d) Limiting the use for publicity purposes of names of licensors or authors
+of the material; or
+
+e) Declining to grant rights under trademark law for use of some trade names,
+trademarks, or service marks; or
+
+f) Requiring indemnification of licensors and authors of that material by
+anyone who conveys the material (or modified versions of it) with contractual
+assumptions of liability to the recipient, for any liability that these contractual
+assumptions directly impose on those licensors and authors.
+
+All other non-permissive additional terms are considered "further restrictions"
+within the meaning of section 10. If the Program as you received it, or any
+part of it, contains a notice stating that it is governed by this License
+along with a term that is a further restriction, you may remove that term.
+If a license document contains a further restriction but permits relicensing
+or conveying under this License, you may add to a covered work material governed
+by the terms of that license document, provided that the further restriction
+does not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you must place,
+in the relevant source files, a statement of the additional terms that apply
+to those files, or a notice indicating where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the form
+of a separately written license, or stated as exceptions; the above requirements
+apply either way.
+
+8. Termination.
+
+You may not propagate or modify a covered work except as expressly provided
+under this License. Any attempt otherwise to propagate or modify it is void,
+and will automatically terminate your rights under this License (including
+any patent licenses granted under the third paragraph of section 11).
+
+However, if you cease all violation of this License, then your license from
+a particular copyright holder is reinstated (a) provisionally, unless and
+until the copyright holder explicitly and finally terminates your license,
+and (b) permanently, if the copyright holder fails to notify you of the violation
+by some reasonable means prior to 60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is reinstated permanently
+if the copyright holder notifies you of the violation by some reasonable means,
+this is the first time you have received notice of violation of this License
+(for any work) from that copyright holder, and you cure the violation prior
+to 30 days after your receipt of the notice.
+
+Termination of your rights under this section does not terminate the licenses
+of parties who have received copies or rights from you under this License.
+If your rights have been terminated and not permanently reinstated, you do
+not qualify to receive new licenses for the same material under section 10.
+
+9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or run a copy
+of the Program. Ancillary propagation of a covered work occurring solely
+as a consequence of using peer-to-peer transmission to receive a copy likewise
+does not require acceptance. However, nothing other than this License grants
+you permission to propagate or modify any covered work. These actions infringe
+copyright if you do not accept this License. Therefore, by modifying or propagating
+a covered work, you indicate your acceptance of this License to do so.
+
+10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically receives
+a license from the original licensors, to run, modify and propagate that work,
+subject to this License. You are not responsible for enforcing compliance
+by third parties with this License.
+
+An "entity transaction" is a transaction transferring control of an organization,
+or substantially all assets of one, or subdividing an organization, or merging
+organizations. If propagation of a covered work results from an entity transaction,
+each party to that transaction who receives a copy of the work also receives
+whatever licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the Corresponding
+Source of the work from the predecessor in interest, if the predecessor has
+it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the rights
+granted or affirmed under this License. For example, you may not impose a
+license fee, royalty, or other charge for exercise of rights granted under
+this License, and you may not initiate litigation (including a cross-claim
+or counterclaim in a lawsuit) alleging that any patent claim is infringed
+by making, using, selling, offering for sale, or importing the Program or
+any portion of it.
+
+11. Patents.
+
+A "contributor" is a copyright holder who authorizes use under this License
+of the Program or a work on which the Program is based. The work thus licensed
+is called the contributor's "contributor version".
+
+A contributor's "essential patent claims" are all patent claims owned or controlled
+by the contributor, whether already acquired or hereafter acquired, that would
+be infringed by some manner, permitted by this License, of making, using,
+or selling its contributor version, but do not include claims that would be
+infringed only as a consequence of further modification of the contributor
+version. For purposes of this definition, "control" includes the right to
+grant patent sublicenses in a manner consistent with the requirements of this
+License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free patent
+license under the contributor's essential patent claims, to make, use, sell,
+offer for sale, import and otherwise run, modify and propagate the contents
+of its contributor version.
+
+In the following three paragraphs, a "patent license" is any express agreement
+or commitment, however denominated, not to enforce a patent (such as an express
+permission to practice a patent or covenant not to sue for patent infringement).
+To "grant" such a patent license to a party means to make such an agreement
+or commitment not to enforce a patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license, and the
+Corresponding Source of the work is not available for anyone to copy, free
+of charge and under the terms of this License, through a publicly available
+network server or other readily accessible means, then you must either (1)
+cause the Corresponding Source to be so available, or (2) arrange to deprive
+yourself of the benefit of the patent license for this particular work, or
+(3) arrange, in a manner consistent with the requirements of this License,
+to extend the patent
+license to downstream recipients. "Knowingly relying" means you have actual
+knowledge that, but for the patent license, your conveying the covered work
+in a country, or your recipient's use of the covered work in a country, would
+infringe one or more identifiable patents in that country that you have reason
+to believe are valid.
+
+If, pursuant to or in connection with a single transaction or arrangement,
+you convey, or propagate by procuring conveyance of, a covered work, and grant
+a patent license to some of the parties receiving the covered work authorizing
+them to use, propagate, modify or convey a specific copy of the covered work,
+then the patent license you grant is automatically extended to all recipients
+of the covered work and works based on it.
+
+A patent license is "discriminatory" if it does not include within the scope
+of its coverage, prohibits the exercise of, or is conditioned on the non-exercise
+of one or more of the rights that are specifically granted under this License.
+You may not convey a covered work if you are a party to an arrangement with
+a third party that is in the business of distributing software, under which
+you make payment to the third party based on the extent of your activity of
+conveying the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory patent
+license (a) in connection with copies of the covered work conveyed by you
+(or copies made from those copies), or (b) primarily for and in connection
+with specific products or compilations that contain the covered work, unless
+you entered into that arrangement, or that patent license was granted, prior
+to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting any implied
+license or other defenses to infringement that may otherwise be available
+to you under applicable patent law.
+
+12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or otherwise)
+that contradict the conditions of this License, they do not excuse you from
+the conditions of this License. If you cannot convey a covered work so as
+to satisfy simultaneously your obligations under this License and any other
+pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey the
+Program, the only way you could satisfy both those terms and this License
+would be to refrain entirely from conveying the Program.
+
+13. Remote Network Interaction; Use with the GNU General Public License.
+
+Notwithstanding any other provision of this License, if you modify the Program,
+your modified version must prominently offer all users interacting with it
+remotely through a computer network (if your version supports such interaction)
+an opportunity to receive the Corresponding Source of your version by providing
+access to the Corresponding Source from a network server at no charge, through
+some standard or customary means of facilitating copying of software. This
+Corresponding Source shall include the Corresponding Source for any work covered
+by version 3 of the GNU General Public License that is incorporated pursuant
+to the following paragraph.
+
+Notwithstanding any other provision of this License, you have permission to
+link or combine any covered work with a work licensed under version 3 of the
+GNU General Public License into a single combined work, and to convey the
+resulting work. The terms of this License will continue to apply to the part
+which is the covered work, but the work with which it is combined will remain
+governed by version 3 of the GNU General Public License.
+
+14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions of the
+GNU Affero General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to address
+new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies
+that a certain numbered version of the GNU Affero General Public License "or
+any later version" applies to it, you have the option of following the terms
+and conditions either of that numbered version or of any later version published
+by the Free Software Foundation. If the Program does not specify a version
+number of the GNU Affero General Public License, you may choose any version
+ever published by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions of
+the GNU Affero General Public License can be used, that proxy's public statement
+of acceptance of a version permanently authorizes you to choose that version
+for the Program.
+
+Later license versions may give you additional or different permissions.
+However, no additional obligations are imposed on any author or copyright
+holder as a result of your choosing to follow a later version.
+
+15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
+LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
+AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR
+OR CORRECTION.
+
+16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
+ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM
+AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
+INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO
+USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
+INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
+PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
+PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided above cannot
+be given local legal effect according to their terms, reviewing courts shall
+apply local law that most closely approximates an absolute waiver of all civil
+liability in connection with the Program, unless a warranty or assumption
+of liability accompanies a copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach
+them to the start of each source file to most effectively state the exclusion
+of warranty; and each file should have at least the "copyright" line and a
+pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+This program is free software: you can redistribute it and/or modify it under
+the terms of the GNU Affero General Public License as published by the Free
+Software Foundation, either version 3 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+details.
+
+You should have received a copy of the GNU Affero General Public License along
+with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If your software can interact with users remotely through a computer network,
+you should also make sure that it provides a way for users to get its source.
+For example, if your program is a web application, its interface could display
+a "Source" link that leads users to an archive of the code. There are many
+ways you could offer source, and different solutions will be better for different
+programs; see section 13 for the specific requirements.
+
+You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary. For
+more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt
new file mode 100644
index 0000000..9a4104b
--- /dev/null
+++ b/LICENSES/Apache-2.0.txt
@@ -0,0 +1,183 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and distribution
+as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct
+or indirect, to cause the direction or management of such entity, whether
+by contract or otherwise, or (ii) ownership of fifty percent (50%) or more
+of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising permissions
+granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation
+or translation of a Source form, including but not limited to compiled object
+code, generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form,
+made available under the License, as indicated by a copyright notice that
+is included in or attached to the work (an example is provided in the Appendix
+below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form,
+that is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative
+Works shall not include works that remain separable from, or merely link (or
+bind by name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative
+Works thereof, that is intentionally submitted to Licensor for inclusion in
+the Work by the copyright owner or by an individual or Legal Entity authorized
+to submit on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication
+sent to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor
+for the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently incorporated
+within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of this
+License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
+Derivative Works of, publicly display, publicly perform, sublicense, and distribute
+the Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of this License,
+each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section) patent
+license to make, have made, use, offer to sell, sell, import, and otherwise
+transfer the Work, where such license applies only to those patent claims
+licensable by such Contributor that are necessarily infringed by their Contribution(s)
+alone or by combination of their Contribution(s) with the Work to which such
+Contribution(s) was submitted. If You institute patent litigation against
+any entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that the Work or a Contribution incorporated within the Work constitutes direct
+or contributory patent infringement, then any patent licenses granted to You
+under this License for that Work shall terminate as of the date such litigation
+is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the Work or
+Derivative Works thereof in any medium, with or without modifications, and
+in Source or Object form, provided that You meet the following conditions:
+
+(a) You must give any other recipients of the Work or Derivative Works a copy
+of this License; and
+
+(b) You must cause any modified files to carry prominent notices stating that
+You changed the files; and
+
+(c) You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source
+form of the Work, excluding those notices that do not pertain to any part
+of the Derivative Works; and
+
+(d) If the Work includes a "NOTICE" text file as part of its distribution,
+then any Derivative Works that You distribute must include a readable copy
+of the attribution notices contained within such NOTICE file, excluding those
+notices that do not pertain to any part of the Derivative Works, in at least
+one of the following places: within a NOTICE text file distributed as part
+of the Derivative Works; within the Source form or documentation, if provided
+along with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents
+of the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works
+that You distribute, alongside or as an addendum to the NOTICE text from the
+Work, provided that such additional attribution notices cannot be construed
+as modifying the License.
+
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction,
+or distribution of Your modifications, or for any such Derivative Works as
+a whole, provided Your use, reproduction, and distribution of the Work otherwise
+complies with the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise, any
+Contribution intentionally submitted for inclusion in the Work by You to the
+Licensor shall be under the terms and conditions of this License, without
+any additional terms or conditions. Notwithstanding the above, nothing herein
+shall supersede or modify the terms of any separate license agreement you
+may have executed with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade names,
+trademarks, service marks, or product names of the Licensor, except as required
+for reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or agreed to
+in writing, Licensor provides the Work (and each Contributor provides its
+Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied, including, without limitation, any warranties
+or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
+A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
+of using or redistributing the Work and assume any risks associated with Your
+exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory, whether
+in tort (including negligence), contract, or otherwise, unless required by
+applicable law (such as deliberate and grossly negligent acts) or agreed to
+in writing, shall any Contributor be liable to You for damages, including
+any direct, indirect, special, incidental, or consequential damages of any
+character arising as a result of this License or out of the use or inability
+to use the Work (including but not limited to damages for loss of goodwill,
+work stoppage, computer failure or malfunction, or any and all other commercial
+damages or losses), even if such Contributor has been advised of the possibility
+of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing the Work
+or Derivative Works thereof, You may choose to offer, and charge a fee for,
+acceptance of support, warranty, indemnity, or other liability obligations
+and/or rights consistent with this License. However, in accepting such obligations,
+You may act only on Your own behalf and on Your sole responsibility, not on
+behalf of any other Contributor, and only if You agree to indemnify, defend,
+and hold each Contributor harmless for any liability incurred by, or claims
+asserted against, such Contributor by reason of your accepting any such warranty
+or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own identifying
+information. (Don't include the brackets!) The text should be enclosed in
+the appropriate comment syntax for the file format. We also recommend that
+a file or class name and description of purpose be included on the same "printed
+page" as the copyright notice for easier identification within third-party
+archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt
new file mode 100644
index 0000000..baa80b5
--- /dev/null
+++ b/LICENSES/BSD-2-Clause.txt
@@ -0,0 +1,22 @@
+Copyright (c) <year> <owner> All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt
new file mode 100644
index 0000000..3d6abd8
--- /dev/null
+++ b/LICENSES/CC-BY-4.0.txt
@@ -0,0 +1,324 @@
+Creative Commons Attribution 4.0 International
+
+Creative Commons Corporation (“Creative Commons”) is not a law firm and does
+not provide legal services or legal advice. Distribution of Creative Commons
+public licenses does not create a lawyer-client or other relationship. Creative
+Commons makes its licenses and related information available on an “as-is”
+basis. Creative Commons gives no warranties regarding its licenses, any material
+licensed under their terms and conditions, or any related information. Creative
+Commons disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and conditions
+that creators and other rights holders may use to share original works of
+authorship and other material subject to copyright and certain other rights
+specified in the public license below. The following considerations are for
+informational purposes only, are not exhaustive, and do not form part of our
+licenses.
+
+Considerations for licensors: Our public licenses are intended for use by
+those authorized to give the public permission to use material in ways otherwise
+restricted by copyright and certain other rights. Our licenses are irrevocable.
+Licensors should read and understand the terms and conditions of the license
+they choose before applying it. Licensors should also secure all rights necessary
+before applying our licenses so that the public can reuse the material as
+expected. Licensors should clearly mark any material not subject to the license.
+This includes other CC-licensed material, or material used under an exception
+or limitation to copyright. More considerations for licensors.
+
+Considerations for the public: By using one of our public licenses, a licensor
+grants the public permission to use the licensed material under specified
+terms and conditions. If the licensor’s permission is not necessary for any
+reason–for example, because of any applicable exception or limitation to copyright–then
+that use is not regulated by the license. Our licenses grant only permissions
+under copyright and certain other rights that a licensor has authority to
+grant. Use of the licensed material may still be restricted for other reasons,
+including because others have copyright or other rights in the material. A
+licensor may make special requests, such as asking that all changes be marked
+or described. Although not required by our licenses, you are encouraged to
+respect those requests where reasonable. More considerations for the public.
+
+Creative Commons Attribution 4.0 International Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree to
+be bound by the terms and conditions of this Creative Commons Attribution
+4.0 International Public License ("Public License"). To the extent this Public
+License may be interpreted as a contract, You are granted the Licensed Rights
+in consideration of Your acceptance of these terms and conditions, and the
+Licensor grants You such rights in consideration of benefits the Licensor
+receives from making the Licensed Material available under these terms and
+conditions.
+
+Section 1 – Definitions.
+
+a. Adapted Material means material subject to Copyright and Similar Rights
+that is derived from or based upon the Licensed Material and in which the
+Licensed Material is translated, altered, arranged, transformed, or otherwise
+modified in a manner requiring permission under the Copyright and Similar
+Rights held by the Licensor. For purposes of this Public License, where the
+Licensed Material is a musical work, performance, or sound recording, Adapted
+Material is always produced where the Licensed Material is synched in timed
+relation with a moving image.
+
+b. Adapter's License means the license You apply to Your Copyright and Similar
+Rights in Your contributions to Adapted Material in accordance with the terms
+and conditions of this Public License.
+
+c. Copyright and Similar Rights means copyright and/or similar rights closely
+related to copyright including, without limitation, performance, broadcast,
+sound recording, and Sui Generis Database Rights, without regard to how the
+rights are labeled or categorized. For purposes of this Public License, the
+rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
+
+d. Effective Technological Measures means those measures that, in the absence
+of proper authority, may not be circumvented under laws fulfilling obligations
+under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996,
+and/or similar international agreements.
+
+e. Exceptions and Limitations means fair use, fair dealing, and/or any other
+exception or limitation to Copyright and Similar Rights that applies to Your
+use of the Licensed Material.
+
+f. Licensed Material means the artistic or literary work, database, or other
+material to which the Licensor applied this Public License.
+
+g. Licensed Rights means the rights granted to You subject to the terms and
+conditions of this Public License, which are limited to all Copyright and
+Similar Rights that apply to Your use of the Licensed Material and that the
+Licensor has authority to license.
+
+h. Licensor means the individual(s) or entity(ies) granting rights under this
+Public License.
+
+i. Share means to provide material to the public by any means or process that
+requires permission under the Licensed Rights, such as reproduction, public
+display, public performance, distribution, dissemination, communication, or
+importation, and to make material available to the public including in ways
+that members of the public may access the material from a place and at a time
+individually chosen by them.
+
+j. Sui Generis Database Rights means rights other than copyright resulting
+from Directive 96/9/EC of the European Parliament and of the Council of 11
+March 1996 on the legal protection of databases, as amended and/or succeeded,
+as well as other essentially equivalent rights anywhere in the world.
+
+k. You means the individual or entity exercising the Licensed Rights under
+this Public License. Your has a corresponding meaning.
+
+Section 2 – Scope.
+
+ a. License grant.
+
+1. Subject to the terms and conditions of this Public License, the Licensor
+hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive,
+irrevocable license to exercise the Licensed Rights in the Licensed Material
+to:
+
+A. reproduce and Share the Licensed Material, in whole or in part; and
+
+ B. produce, reproduce, and Share Adapted Material.
+
+2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions
+and Limitations apply to Your use, this Public License does not apply, and
+You do not need to comply with its terms and conditions.
+
+3. Term. The term of this Public License is specified in Section 6(a).
+
+4. Media and formats; technical modifications allowed. The Licensor authorizes
+You to exercise the Licensed Rights in all media and formats whether now known
+or hereafter created, and to make technical modifications necessary to do
+so. The Licensor waives and/or agrees not to assert any right or authority
+to forbid You from making technical modifications necessary to exercise the
+Licensed Rights, including technical modifications necessary to circumvent
+Effective Technological Measures. For purposes of this Public License, simply
+making modifications authorized by this Section 2(a)(4) never produces Adapted
+Material.
+
+ 5. Downstream recipients.
+
+A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed
+Material automatically receives an offer from the Licensor to exercise the
+Licensed Rights under the terms and conditions of this Public License.
+
+B. No downstream restrictions. You may not offer or impose any additional
+or different terms or conditions on, or apply any Effective Technological
+Measures to, the Licensed Material if doing so restricts exercise of the Licensed
+Rights by any recipient of the Licensed Material.
+
+6. No endorsement. Nothing in this Public License constitutes or may be construed
+as permission to assert or imply that You are, or that Your use of the Licensed
+Material is, connected with, or sponsored, endorsed, or granted official status
+by, the Licensor or others designated to receive attribution as provided in
+Section 3(a)(1)(A)(i).
+
+b. Other rights.
+
+1. Moral rights, such as the right of integrity, are not licensed under this
+Public License, nor are publicity, privacy, and/or other similar personality
+rights; however, to the extent possible, the Licensor waives and/or agrees
+not to assert any such rights held by the Licensor to the limited extent necessary
+to allow You to exercise the Licensed Rights, but not otherwise.
+
+2. Patent and trademark rights are not licensed under this Public License.
+
+3. To the extent possible, the Licensor waives any right to collect royalties
+from You for the exercise of the Licensed Rights, whether directly or through
+a collecting society under any voluntary or waivable statutory or compulsory
+licensing scheme. In all other cases the Licensor expressly reserves any right
+to collect such royalties.
+
+Section 3 – License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the following
+conditions.
+
+ a. Attribution.
+
+1. If You Share the Licensed Material (including in modified form), You must:
+
+A. retain the following if it is supplied by the Licensor with the Licensed
+Material:
+
+i. identification of the creator(s) of the Licensed Material and any others
+designated to receive attribution, in any reasonable manner requested by the
+Licensor (including by pseudonym if designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of warranties;
+
+v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
+
+B. indicate if You modified the Licensed Material and retain an indication
+of any previous modifications; and
+
+C. indicate the Licensed Material is licensed under this Public License, and
+include the text of, or the URI or hyperlink to, this Public License.
+
+2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner
+based on the medium, means, and context in which You Share the Licensed Material.
+For example, it may be reasonable to satisfy the conditions by providing a
+URI or hyperlink to a resource that includes the required information.
+
+3. If requested by the Licensor, You must remove any of the information required
+by Section 3(a)(1)(A) to the extent reasonably practicable.
+
+4. If You Share Adapted Material You produce, the Adapter's License You apply
+must not prevent recipients of the Adapted Material from complying with this
+Public License.
+
+Section 4 – Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that apply to
+Your use of the Licensed Material:
+
+a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract,
+reuse, reproduce, and Share all or a substantial portion of the contents of
+the database;
+
+b. if You include all or a substantial portion of the database contents in
+a database in which You have Sui Generis Database Rights, then the database
+in which You have Sui Generis Database Rights (but not its individual contents)
+is Adapted Material; and
+
+c. You must comply with the conditions in Section 3(a) if You Share all or
+a substantial portion of the contents of the database.
+For the avoidance of doubt, this Section 4 supplements and does not replace
+Your obligations under this Public License where the Licensed Rights include
+other Copyright and Similar Rights.
+
+Section 5 – Disclaimer of Warranties and Limitation of Liability.
+
+a. Unless otherwise separately undertaken by the Licensor, to the extent possible,
+the Licensor offers the Licensed Material as-is and as-available, and makes
+no representations or warranties of any kind concerning the Licensed Material,
+whether express, implied, statutory, or other. This includes, without limitation,
+warranties of title, merchantability, fitness for a particular purpose, non-infringement,
+absence of latent or other defects, accuracy, or the presence or absence of
+errors, whether or not known or discoverable. Where disclaimers of warranties
+are not allowed in full or in part, this disclaimer may not apply to You.
+
+b. To the extent possible, in no event will the Licensor be liable to You
+on any legal theory (including, without limitation, negligence) or otherwise
+for any direct, special, indirect, incidental, consequential, punitive, exemplary,
+or other losses, costs, expenses, or damages arising out of this Public License
+or use of the Licensed Material, even if the Licensor has been advised of
+the possibility of such losses, costs, expenses, or damages. Where a limitation
+of liability is not allowed in full or in part, this limitation may not apply
+to You.
+
+c. The disclaimer of warranties and limitation of liability provided above
+shall be interpreted in a manner that, to the extent possible, most closely
+approximates an absolute disclaimer and waiver of all liability.
+
+Section 6 – Term and Termination.
+
+a. This Public License applies for the term of the Copyright and Similar Rights
+licensed here. However, if You fail to comply with this Public License, then
+Your rights under this Public License terminate automatically.
+
+b. Where Your right to use the Licensed Material has terminated under Section
+6(a), it reinstates:
+
+1. automatically as of the date the violation is cured, provided it is cured
+within 30 days of Your discovery of the violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+c. For the avoidance of doubt, this Section 6(b) does not affect any right
+the Licensor may have to seek remedies for Your violations of this Public
+License.
+
+d. For the avoidance of doubt, the Licensor may also offer the Licensed Material
+under separate terms or conditions or stop distributing the Licensed Material
+at any time; however, doing so will not terminate this Public License.
+
+ e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
+
+Section 7 – Other Terms and Conditions.
+
+a. The Licensor shall not be bound by any additional or different terms or
+conditions communicated by You unless expressly agreed.
+
+b. Any arrangements, understandings, or agreements regarding the Licensed
+Material not stated herein are separate from and independent of the terms
+and conditions of this Public License.
+
+Section 8 – Interpretation.
+
+a. For the avoidance of doubt, this Public License does not, and shall not
+be interpreted to, reduce, limit, restrict, or impose conditions on any use
+of the Licensed Material that could lawfully be made without permission under
+this Public License.
+
+b. To the extent possible, if any provision of this Public License is deemed
+unenforceable, it shall be automatically reformed to the minimum extent necessary
+to make it enforceable. If the provision cannot be reformed, it shall be severed
+from this Public License without affecting the enforceability of the remaining
+terms and conditions.
+
+c. No term or condition of this Public License will be waived and no failure
+to comply consented to unless expressly agreed to by the Licensor.
+
+d. Nothing in this Public License constitutes or may be interpreted as a limitation
+upon, or waiver of, any privileges and immunities that apply to the Licensor
+or You, including from the legal processes of any jurisdiction or authority.
+
+Creative Commons is not a party to its public licenses. Notwithstanding, Creative
+Commons may elect to apply one of its public licenses to material it publishes
+and in those instances will be considered the “Licensor.” Except for the limited
+purpose of indicating that material is shared under a Creative Commons public
+license or as otherwise permitted by the Creative Commons policies published
+at creativecommons.org/policies, Creative Commons does not authorize the use
+of the trademark “Creative Commons” or any other trademark or logo of Creative
+Commons without its prior written consent including, without limitation, in
+connection with any unauthorized modifications to any of its public licenses
+or any other arrangements, understandings, or agreements concerning use of
+licensed material. For the avoidance of doubt, this paragraph does not form
+part of the public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt
new file mode 100644
index 0000000..0e259d4
--- /dev/null
+++ b/LICENSES/CC0-1.0.txt
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt
new file mode 100644
index 0000000..f0fd20a
--- /dev/null
+++ b/LICENSES/MIT.txt
@@ -0,0 +1,20 @@
+MIT License
+
+Copyright (c) <year> <copyright holders>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README b/README
deleted file mode 100644
index c3287bd..0000000
--- a/README
+++ /dev/null
@@ -1,11 +0,0 @@
-db-fakedisplay - Infoscreen for DB departures
----------------------------------------------
-
-* <http://finalrewind.org/projects/db-fakedisplay/>
-
-Dependencies
-------------
-
- * perl >= 5.10
- * Mojolicious
- * Travel::Status::DE::DeutscheBahn >= 0.04
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7ba7c40
--- /dev/null
+++ b/README.md
@@ -0,0 +1,178 @@
+db-infoscreen - App/Infoscreen for Railway Departures in Germany
+---
+
+[db-infoscreen](https://finalrewind.org/projects/db-fakedisplay/) (formerly
+db-fakedisplay) shows departures at public transit stops in most of Germany,
+Switzerland, Austria, Luxembourg, Ireland, and parts of the USA. It can serve
+both as infoscreen and web application for mobile usage. Depending on backend
+support, it can provide details on individual departures such as a map of the
+scheduled route, expected occupancy, and carriage formation.
+
+There's a public [db-infoscreen service on
+finalrewind.org](https://dbf.finalrewind.org/). You can also host your own
+instance via carton/cpanminus or Docker if you like, see the Setup notes below.
+
+
+Dependencies
+---
+
+ * perl >= 5.20
+ * carton or cpanminus
+ * build-essential
+ * git
+
+Installation
+---
+
+After installing the dependencies, clone the repository using git, e.g.
+
+```
+git clone https://git.finalrewind.org/db-fakedisplay
+```
+
+Make sure that all files (including `.git`, which is used to determine the
+software version) are readable by your www user, and follow the steps in the
+next sections.
+
+Perl Dependencies
+---
+
+db-infoscreen depends on a set of Perl modules which are documented in
+`cpanfile`. After installing the dependencies mentioned above, you can use
+carton or cpanminus to install Perl dependencies locally.
+
+In the project root directory (where `cpanfile` resides), run either
+
+```
+carton install
+```
+
+or
+
+```
+cpanm --installdeps .
+```
+
+and set `PERL5LIB=.../local/lib/perl5` before running index.pl or wrap it
+with `carton exec hypnotoad index.pl`.
+
+Note that you should provide imprint and privacy policy pages. Depending on
+traffic volume, you may also want to increase the amount of worker processes
+and install a caching proxy in front of DBF. See the Setup notes below.
+
+To update your DBF installation, run `git pull`, ensure that all files are
+readable by your www user, and re-run `carton install` or `cpanm --installdeps
+.`.
+
+Installation with Docker
+---
+
+A db-infoscreen image is available on Docker Hub. You can install and run it
+as follows:
+
+```
+docker pull derfnull/db-fakedisplay:latest
+docker run --rm -p 8000:8092 -v "$(pwd)/templates:/app/ext-templates:ro" db-fakedisplay:latest
+```
+
+This will make the web service available on port 8000. Note that you should
+provide imprint and privacy policy pages, see the Setup notes below.
+
+Use `docker run -e DBFAKEDISPLAY_WORKERS=4 ...` and similar to pass environment
+variables to the db-infoscreen service.
+
+To update your Docker installation, fetch a new image from Docker Hub and
+re-start the container.
+
+Setup
+---
+
+In hypnotoad mode (recommended), db-infoscreen respects the following environment variables:
+
+| Variable | Default | Description |
+| :------- | :------ | :---------- |
+| DBFAKEDISPLAY\_LISTEN | `http://*:8092` | IP and Port for web service |
+| DBFAKEDISPLAY\_STATS | _None_ | File in which the total count of backend API requests (excluding those answered from cache) is written |
+| DBFAKEDISPLAY\_IRIS\_CACHE | `/tmp/dbf-iris-mian` | Directory for IRIS schedule cache |
+| DBFAKEDISPLAY\_IRISRT\_CACHE | `/tmp/dbf-iris-realtime` | Directory for IRIS realtime cache |
+| DBFAKEDISPLAY\_WORKERS | 2 | Number of worker processes (i.e., maximum amount of concurrent requests) |
+
+Set these as needed, create `templates/imprint.html.ep` (imprint) and
+`templates/privacy.html.ep` (privacy policy), and configure your web server to
+pass requests for db-infoscreen to the appropriate port. See the
+`examples` directory for imprint and privacy policy samples.
+
+You can then use a service supervisor of your choice to run **hypnotoad index.pl**
+(using Mojolicious' hypnotoad server). See `examples/db-infoscreen.service`
+for a systemd unit example.
+
+For a quick&dirty setup on low-traffic sites you can also use **morbo index.pl**
+or **perl index.pl daemon -m production**. In this case, DBFAKEDISPLAY\_LISTEN
+and DBFAKEDISPLAY\_WORKERS have no effect. Morbo accepts IP and port
+configuration using the `-l`/`--listen` switch (default: `http://*:3000`);
+Daemon mode respects the MOJO\_LISTEN environment variable (default: `http://*:3000`).
+
+For public-facing installations, you may want to enable caching in the reverse
+proxy serving DBF. See `examples/nginx-cache.conf` and
+`examples/nginx-site.conf` for nginx examples.
+
+All code in this repository may be used under the terms of the BSD-2-Clause
+(db-infoscreen, see COPYING) and MIT (jquery, jqueryui, and marquee libraries;
+see the respective files) licenses. Attribution is appreciated.
+
+Background Data Updates
+---
+
+db-infoscreen can use <https://lib.finalrewind.org/dbdb/db_zugbildung_v1.json>
+to show scheduled ICE/IC types (ICE 1/2/3/4/T, IC 1/2), wagon orders, and other
+attributes. It expects the file to be provided in `share/zugbildungsplan.json`.
+
+As this information is updated regularly, the file is not shipped as part of
+this db-infoscreen distribution. It is recommended to retrieve it a few minutes
+after midnight via a daily cronjob. See `examples/dbf_update_zugbildungsplan`
+for a shell script.
+
+DBF will periodically reload `share/zugbildungsplan.json`. You can use your
+service supervisor (e.g. `systemctl reload db-infoscreen`) to force an
+immediate reload. You may also ignore the file; it is entirely optional.
+
+System requirements
+---
+
+Resource requirements depend on usage. For a few requests per second, about
+200MB (600k inodes) cache and one or two CPU cores should be sufficient.
+db-infoscreen typically needs 50MB RAM per worker process, though calculating
+with 100MB per worker is recommended to leave a safety margin.
+
+Licensing
+---
+
+This project follows the REUSE specification. The copyright of individual files
+is documented in the file's header or in .reuse/dep5. The referenced licenses
+are stored in the LICENSES directory.
+
+The program code of db-infoscreen is licensed under the terms of the GNU AGPL
+v3. HTML Templates and SASS/CSS layout are licensed under the terms of the
+2-Clause BSD License. This means that you are free to host your own
+db-infoscreen instance, both for personal/internal and public use, under the
+following conditions.
+
+* You are free to change HTML/SASS/CSS templates as you see fit (though you
+ must not remove the copyright headers).
+* If you make changes to the program code, that is, a file below lib/ or a
+ db-infoscreen javascript file below public/static/js/, you must make those
+ changes available to the public.
+
+The easiest way of making changes available is by maintaining a public fork of
+the Git repository. A tarball is also acceptable. Please change `source_url` in
+`lib/DBInfoscreen.pm` to point to your Git repository / source archive if you
+are using a version with custom changes.
+
+Resources
+---
+
+Mirrors of the db-infoscreen repository are available at
+
+* [Chaosdorf](https://chaosdorf.de/git/derf/db-infoscreen)
+* [git.finalrewind.org](https://git.finalrewind.org/db-fakedisplay/)
+* [GitHub](https://github.com/derf/db-fakedisplay)
diff --git a/cpanfile b/cpanfile
new file mode 100644
index 0000000..95ce624
--- /dev/null
+++ b/cpanfile
@@ -0,0 +1,18 @@
+requires 'Cache';
+requires 'DateTime';
+requires 'DateTime::Format::Strptime';
+requires 'File::Slurp';
+requires 'GIS::Distance';
+requires 'GIS::Distance::Fast';
+requires 'IO::Socket::Socks', '>= 0.64';
+requires 'IO::Socket::SSL', '>= 2.009';
+requires 'JSON';
+requires 'JSON::XS';
+requires 'List::UtilsBy';
+requires 'Mojolicious';
+requires 'Travel::Status::DE::DBRIS', '>= 0.06';
+requires 'Travel::Status::DE::EFA', '>= 3.13';
+requires 'Travel::Status::DE::HAFAS', '>= 5.06';
+requires 'Travel::Status::DE::IRIS';
+requires 'Travel::Status::MOTIS';
+requires 'XML::LibXML';
diff --git a/cpanfile.snapshot b/cpanfile.snapshot
new file mode 100644
index 0000000..6ee75bf
--- /dev/null
+++ b/cpanfile.snapshot
@@ -0,0 +1,2418 @@
+# carton snapshot format: version 1.0
+DISTRIBUTIONS
+ Alien-Build-2.84
+ pathname: P/PL/PLICEASE/Alien-Build-2.84.tar.gz
+ provides:
+ Alien::Base 2.84
+ Alien::Base::PkgConfig 2.84
+ Alien::Base::Wrapper 2.84
+ Alien::Build 2.84
+ Alien::Build::CommandSequence 2.84
+ Alien::Build::Helper 2.84
+ Alien::Build::Interpolate 2.84
+ Alien::Build::Interpolate::Default 2.84
+ Alien::Build::Interpolate::Helper 2.84
+ Alien::Build::Log 2.84
+ Alien::Build::Log::Abbreviate 2.84
+ Alien::Build::Log::Default 2.84
+ Alien::Build::MM 2.84
+ Alien::Build::Meta 2.84
+ Alien::Build::Plugin 2.84
+ Alien::Build::Plugin::Build::Autoconf 2.84
+ Alien::Build::Plugin::Build::CMake 2.84
+ Alien::Build::Plugin::Build::Copy 2.84
+ Alien::Build::Plugin::Build::MSYS 2.84
+ Alien::Build::Plugin::Build::Make 2.84
+ Alien::Build::Plugin::Build::SearchDep 2.84
+ Alien::Build::Plugin::Core::CleanInstall 2.84
+ Alien::Build::Plugin::Core::Download 2.84
+ Alien::Build::Plugin::Core::FFI 2.84
+ Alien::Build::Plugin::Core::Gather 2.84
+ Alien::Build::Plugin::Core::Legacy 2.84
+ Alien::Build::Plugin::Core::Override 2.84
+ Alien::Build::Plugin::Core::Setup 2.84
+ Alien::Build::Plugin::Core::Tail 2.84
+ Alien::Build::Plugin::Decode::DirListing 2.84
+ Alien::Build::Plugin::Decode::DirListingFtpcopy 2.84
+ Alien::Build::Plugin::Decode::HTML 2.84
+ Alien::Build::Plugin::Decode::Mojo 2.84
+ Alien::Build::Plugin::Digest::Negotiate 2.84
+ Alien::Build::Plugin::Digest::SHA 2.84
+ Alien::Build::Plugin::Digest::SHAPP 2.84
+ Alien::Build::Plugin::Download::Negotiate 2.84
+ Alien::Build::Plugin::Extract::ArchiveTar 2.84
+ Alien::Build::Plugin::Extract::ArchiveZip 2.84
+ Alien::Build::Plugin::Extract::CommandLine 2.84
+ Alien::Build::Plugin::Extract::Directory 2.84
+ Alien::Build::Plugin::Extract::File 2.84
+ Alien::Build::Plugin::Extract::Negotiate 2.84
+ Alien::Build::Plugin::Fetch::CurlCommand 2.84
+ Alien::Build::Plugin::Fetch::HTTPTiny 2.84
+ Alien::Build::Plugin::Fetch::LWP 2.84
+ Alien::Build::Plugin::Fetch::Local 2.84
+ Alien::Build::Plugin::Fetch::LocalDir 2.84
+ Alien::Build::Plugin::Fetch::NetFTP 2.84
+ Alien::Build::Plugin::Fetch::Wget 2.84
+ Alien::Build::Plugin::Gather::IsolateDynamic 2.84
+ Alien::Build::Plugin::PkgConfig::CommandLine 2.84
+ Alien::Build::Plugin::PkgConfig::LibPkgConf 2.84
+ Alien::Build::Plugin::PkgConfig::MakeStatic 2.84
+ Alien::Build::Plugin::PkgConfig::Negotiate 2.84
+ Alien::Build::Plugin::PkgConfig::PP 2.84
+ Alien::Build::Plugin::Prefer::BadVersion 2.84
+ Alien::Build::Plugin::Prefer::GoodVersion 2.84
+ Alien::Build::Plugin::Prefer::SortVersions 2.84
+ Alien::Build::Plugin::Probe::CBuilder 2.84
+ Alien::Build::Plugin::Probe::CommandLine 2.84
+ Alien::Build::Plugin::Probe::Vcpkg 2.84
+ Alien::Build::Plugin::Test::Mock 2.84
+ Alien::Build::PluginMeta 2.84
+ Alien::Build::Temp 2.84
+ Alien::Build::TempDir 2.84
+ Alien::Build::Util 2.84
+ Alien::Build::Version::Basic 2.84
+ Alien::Build::rc 2.84
+ Alien::Role 2.84
+ Alien::Util 2.84
+ Test::Alien 2.84
+ Test::Alien::Build 2.84
+ Test::Alien::CanCompile 2.84
+ Test::Alien::CanPlatypus 2.84
+ Test::Alien::Diag 2.84
+ Test::Alien::Run 2.84
+ Test::Alien::Synthetic 2.84
+ alienfile 2.84
+ requirements:
+ Capture::Tiny 0.17
+ Digest::SHA 0
+ ExtUtils::CBuilder 0
+ ExtUtils::MakeMaker 6.64
+ ExtUtils::ParseXS 3.30
+ FFI::CheckLib 0.11
+ File::Which 1.10
+ File::chdir 0
+ JSON::PP 0
+ List::Util 1.33
+ Path::Tiny 0.077
+ PkgConfig 0.14026
+ Test2::API 1.302096
+ Text::ParseWords 3.26
+ parent 0
+ perl 5.008004
+ Alien-Build-Plugin-Download-GitLab-0.01
+ pathname: P/PL/PLICEASE/Alien-Build-Plugin-Download-GitLab-0.01.tar.gz
+ provides:
+ Alien::Build::Plugin::Download::GitLab 0.01
+ requirements:
+ Alien::Build::Plugin 0
+ ExtUtils::MakeMaker 0
+ JSON::PP 0
+ Path::Tiny 0
+ URI 0
+ URI::Escape 0
+ perl 5.008004
+ Alien-Libxml2-0.20
+ pathname: P/PL/PLICEASE/Alien-Libxml2-0.20.tar.gz
+ provides:
+ Alien::Libxml2 0.20
+ requirements:
+ Alien::Base 2.37
+ Alien::Build 2.37
+ Alien::Build::MM 2.37
+ Alien::Build::Plugin::Build::SearchDep 0.35
+ Alien::Build::Plugin::Download::GitLab 0
+ Alien::Build::Plugin::Prefer::BadVersion 1.05
+ Alien::Build::Plugin::Probe::Vcpkg 0
+ ExtUtils::CBuilder 0
+ ExtUtils::MakeMaker 6.52
+ perl 5.006
+ B-Hooks-EndOfScope-0.28
+ pathname: E/ET/ETHER/B-Hooks-EndOfScope-0.28.tar.gz
+ provides:
+ B::Hooks::EndOfScope 0.28
+ B::Hooks::EndOfScope::PP 0.28
+ B::Hooks::EndOfScope::XS 0.28
+ requirements:
+ ExtUtils::MakeMaker 0
+ Hash::Util::FieldHash 0
+ Module::Implementation 0.05
+ Scalar::Util 0
+ Sub::Exporter::Progressive 0.001006
+ Text::ParseWords 0
+ Tie::Hash 0
+ Variable::Magic 0.48
+ perl 5.006001
+ strict 0
+ warnings 0
+ B-Hooks-OP-Check-0.22
+ pathname: E/ET/ETHER/B-Hooks-OP-Check-0.22.tar.gz
+ provides:
+ B::Hooks::OP::Check 0.22
+ requirements:
+ DynaLoader 0
+ ExtUtils::Depends 0.302
+ ExtUtils::MakeMaker 0
+ parent 0
+ perl 5.008001
+ strict 0
+ warnings 0
+ Cache-2.11
+ pathname: S/SH/SHLOMIF/Cache-2.11.tar.gz
+ provides:
+ Cache 2.11
+ Cache::Entry 2.11
+ Cache::File 2.11
+ Cache::File::Entry 2.11
+ Cache::File::Handle undef
+ Cache::File::Heap 2.11
+ Cache::IOString undef
+ Cache::Memory 2.11
+ Cache::Memory::Entry 2.11
+ Cache::Memory::HeapElem undef
+ Cache::Null 2.11
+ Cache::Null::Entry 2.11
+ Cache::RemovalStrategy 2.11
+ Cache::RemovalStrategy::FIFO undef
+ Cache::RemovalStrategy::LRU undef
+ Cache::Tester 2.11
+ requirements:
+ DB_File 1.72
+ Date::Parse 2.24
+ Digest::SHA 0
+ Fcntl 1.03
+ File::Find 0
+ File::NFSLock 1.20
+ File::Path 1.00
+ File::Spec 0.8
+ Heap::Fibonacci 0.01
+ IO::File 1.08
+ IO::Handle 1.21
+ IO::String 1.02
+ Module::Build 0
+ Storable 1.00
+ Symbol 1.02
+ Test::More 0.45
+ perl 5.006
+ Canary-Stability-2013
+ pathname: M/ML/MLEHMANN/Canary-Stability-2013.tar.gz
+ provides:
+ Canary::Stability 2013
+ requirements:
+ ExtUtils::MakeMaker 0
+ Capture-Tiny-0.50
+ pathname: D/DA/DAGOLDEN/Capture-Tiny-0.50.tar.gz
+ provides:
+ Capture::Tiny 0.50
+ requirements:
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 6.17
+ File::Spec 0
+ File::Temp 0
+ IO::Handle 0
+ Scalar::Util 0
+ perl 5.006
+ strict 0
+ warnings 0
+ Class-Accessor-0.51
+ pathname: K/KA/KASEI/Class-Accessor-0.51.tar.gz
+ provides:
+ Class::Accessor 0.51
+ Class::Accessor::Fast 0.51
+ Class::Accessor::Faster 0.51
+ requirements:
+ ExtUtils::MakeMaker 0
+ base 1.01
+ Class-Data-Inheritable-0.10
+ pathname: R/RS/RSHERER/Class-Data-Inheritable-0.10.tar.gz
+ provides:
+ Class::Data::Inheritable 0.10
+ requirements:
+ ExtUtils::MakeMaker 0
+ Class-Inspector-1.36
+ pathname: P/PL/PLICEASE/Class-Inspector-1.36.tar.gz
+ provides:
+ Class::Inspector 1.36
+ Class::Inspector::Functions 1.36
+ requirements:
+ ExtUtils::MakeMaker 0
+ File::Spec 0.80
+ base 0
+ perl 5.008
+ Class-Measure-0.10
+ pathname: B/BL/BLUEFEET/Class-Measure-0.10.tar.gz
+ provides:
+ Class::Measure 0.10
+ Class::Measure::Length 0.10
+ requirements:
+ Carp 0
+ Module::Build::Tiny 0.035
+ Scalar::Util 0
+ Sub::Exporter 0.982
+ perl 5.008001
+ Class-Singleton-1.6
+ pathname: S/SH/SHAY/Class-Singleton-1.6.tar.gz
+ provides:
+ Class::Singleton 1.6
+ requirements:
+ ExtUtils::MakeMaker 6.64
+ perl 5.008001
+ strict 0
+ warnings 0
+ Clone-0.47
+ pathname: A/AT/ATOOMIC/Clone-0.47.tar.gz
+ provides:
+ Clone 0.47
+ requirements:
+ ExtUtils::MakeMaker 0
+ Clone-PP-1.08
+ pathname: N/NE/NEILB/Clone-PP-1.08.tar.gz
+ provides:
+ Clone::PP 1.08
+ requirements:
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ perl 5.006
+ strict 0
+ vars 0
+ warnings 0
+ Const-Fast-0.014
+ pathname: L/LE/LEONT/Const-Fast-0.014.tar.gz
+ provides:
+ Const::Fast 0.014
+ requirements:
+ Carp 0
+ Module::Build::Tiny 0.021
+ Scalar::Util 0
+ Storable 0
+ Sub::Exporter::Progressive 0.001007
+ perl 5.008
+ strict 0
+ warnings 0
+ Data-OptList-0.114
+ pathname: R/RJ/RJBS/Data-OptList-0.114.tar.gz
+ provides:
+ Data::OptList 0.114
+ requirements:
+ ExtUtils::MakeMaker 6.78
+ List::Util 0
+ Params::Util 0
+ Sub::Install 0.921
+ perl 5.012
+ strict 0
+ warnings 0
+ 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
+ DateTime::TimeZone 2.44
+ Dist::CheckConflicts 0.02
+ ExtUtils::MakeMaker 0
+ POSIX 0
+ Params::ValidationCompiler 0.26
+ Scalar::Util 0
+ Specio 0.50
+ Specio::Declare 0
+ Specio::Exporter 0
+ Specio::Library::Builtins 0
+ Specio::Library::Numeric 0
+ Specio::Library::String 0
+ Specio::Subs 0
+ Try::Tiny 0
+ XSLoader 0
+ integer 0
+ namespace::autoclean 0.19
+ overload 0
+ parent 0
+ perl 5.008004
+ 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:
+ DateTime::Format::Strptime 1.79
+ DateTime::Format::Strptime::Types 1.79
+ requirements:
+ Carp 0
+ DateTime 1.00
+ DateTime::Locale 1.30
+ DateTime::Locale::Base 0
+ DateTime::Locale::FromData 0
+ DateTime::TimeZone 2.09
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ Params::ValidationCompiler 0
+ Specio 0.33
+ Specio::Declare 0
+ Specio::Exporter 0
+ Specio::Library::Builtins 0
+ Specio::Library::String 0
+ Try::Tiny 0
+ constant 0
+ parent 0
+ strict 0
+ warnings 0
+ DateTime-Locale-1.45
+ pathname: D/DR/DROLSKY/DateTime-Locale-1.45.tar.gz
+ provides:
+ 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
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ File::ShareDir 0
+ File::ShareDir::Install 0.06
+ File::Spec 0
+ List::Util 1.45
+ Params::ValidationCompiler 0.13
+ Specio::Declare 0
+ Specio::Library::String 0
+ Storable 0
+ namespace::autoclean 0.19
+ perl 5.008004
+ strict 0
+ warnings 0
+ 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
+ ExtUtils::MakeMaker 0
+ File::Basename 0
+ File::Compare 0
+ File::Find 0
+ File::Spec 0
+ List::Util 1.33
+ Module::Runtime 0
+ Params::ValidationCompiler 0.13
+ Specio::Library::Builtins 0
+ Specio::Library::String 0
+ Try::Tiny 0
+ constant 0
+ namespace::autoclean 0
+ parent 0
+ perl 5.008004
+ strict 0
+ warnings 0
+ Devel-StackTrace-2.05
+ pathname: D/DR/DROLSKY/Devel-StackTrace-2.05.tar.gz
+ provides:
+ Devel::StackTrace 2.05
+ Devel::StackTrace::Frame 2.05
+ requirements:
+ ExtUtils::MakeMaker 0
+ File::Spec 0
+ Scalar::Util 0
+ overload 0
+ perl 5.006
+ strict 0
+ warnings 0
+ Dist-CheckConflicts-0.11
+ pathname: D/DO/DOY/Dist-CheckConflicts-0.11.tar.gz
+ provides:
+ Dist::CheckConflicts 0.11
+ requirements:
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 6.30
+ Module::Runtime 0.009
+ base 0
+ strict 0
+ warnings 0
+ Encode-Locale-1.05
+ pathname: G/GA/GAAS/Encode-Locale-1.05.tar.gz
+ provides:
+ Encode::Locale 1.05
+ requirements:
+ Encode 2
+ Encode::Alias 0
+ ExtUtils::MakeMaker 0
+ perl 5.008
+ Eval-Closure-0.14
+ pathname: D/DO/DOY/Eval-Closure-0.14.tar.gz
+ provides:
+ Eval::Closure 0.14
+ requirements:
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ Scalar::Util 0
+ constant 0
+ overload 0
+ strict 0
+ warnings 0
+ Exception-Class-1.45
+ pathname: D/DR/DROLSKY/Exception-Class-1.45.tar.gz
+ provides:
+ Exception::Class 1.45
+ Exception::Class::Base 1.45
+ requirements:
+ Carp 0
+ Class::Data::Inheritable 0.02
+ Devel::StackTrace 2.00
+ ExtUtils::MakeMaker 0
+ Scalar::Util 0
+ base 0
+ overload 0
+ perl 5.008001
+ strict 0
+ warnings 0
+ Exporter-Tiny-1.006002
+ pathname: T/TO/TOBYINK/Exporter-Tiny-1.006002.tar.gz
+ provides:
+ Exporter::Shiny 1.006002
+ Exporter::Tiny 1.006002
+ requirements:
+ ExtUtils::MakeMaker 6.17
+ perl 5.006001
+ ExtUtils-Config-0.010
+ pathname: L/LE/LEONT/ExtUtils-Config-0.010.tar.gz
+ provides:
+ ExtUtils::Config 0.010
+ ExtUtils::Config::MakeMaker 0.010
+ requirements:
+ Data::Dumper 0
+ ExtUtils::MakeMaker 0
+ ExtUtils::MakeMaker::Config 0
+ perl 5.006
+ strict 0
+ warnings 0
+ ExtUtils-Depends-0.8002
+ pathname: E/ET/ETJ/ExtUtils-Depends-0.8002.tar.gz
+ provides:
+ ExtUtils::Depends 0.8002
+ requirements:
+ Data::Dumper 0
+ ExtUtils::MakeMaker 7.44
+ File::Spec 0
+ IO::File 0
+ perl 5.006
+ ExtUtils-Helpers-0.028
+ pathname: L/LE/LEONT/ExtUtils-Helpers-0.028.tar.gz
+ provides:
+ ExtUtils::Helpers 0.028
+ ExtUtils::Helpers::Unix 0.028
+ ExtUtils::Helpers::VMS 0.028
+ ExtUtils::Helpers::Windows 0.028
+ requirements:
+ Carp 0
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ File::Basename 0
+ File::Copy 0
+ File::Spec::Functions 0
+ Text::ParseWords 3.24
+ strict 0
+ warnings 0
+ ExtUtils-InstallPaths-0.014
+ pathname: L/LE/LEONT/ExtUtils-InstallPaths-0.014.tar.gz
+ provides:
+ ExtUtils::InstallPaths 0.014
+ requirements:
+ Carp 0
+ ExtUtils::Config 0.009
+ ExtUtils::MakeMaker 0
+ File::Spec 0
+ perl 5.008
+ strict 0
+ warnings 0
+ FFI-CheckLib-0.31
+ pathname: P/PL/PLICEASE/FFI-CheckLib-0.31.tar.gz
+ provides:
+ FFI::CheckLib 0.31
+ requirements:
+ ExtUtils::MakeMaker 0
+ File::Which 0
+ List::Util 1.33
+ perl 5.006
+ File-Listing-6.16
+ pathname: P/PL/PLICEASE/File-Listing-6.16.tar.gz
+ provides:
+ File::Listing 6.16
+ File::Listing::apache 6.16
+ File::Listing::dosftp 6.16
+ File::Listing::netware 6.16
+ File::Listing::unix 6.16
+ File::Listing::vms 6.16
+ requirements:
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ HTTP::Date 0
+ perl 5.006
+ File-NFSLock-1.29
+ pathname: B/BB/BBB/File-NFSLock-1.29.tar.gz
+ provides:
+ File::NFSLock 1.29
+ requirements:
+ ExtUtils::MakeMaker 0
+ File-ShareDir-1.118
+ pathname: R/RE/REHSACK/File-ShareDir-1.118.tar.gz
+ provides:
+ File::ShareDir 1.118
+ requirements:
+ Carp 0
+ Class::Inspector 1.12
+ ExtUtils::MakeMaker 0
+ File::ShareDir::Install 0.13
+ File::Spec 0.80
+ perl 5.008001
+ warnings 0
+ File-ShareDir-Install-0.14
+ pathname: E/ET/ETHER/File-ShareDir-Install-0.14.tar.gz
+ provides:
+ File::ShareDir::Install 0.14
+ requirements:
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ File::Spec 0
+ IO::Dir 0
+ perl 5.006
+ strict 0
+ warnings 0
+ File-Slurp-9999.32
+ pathname: C/CA/CAPOEIRAB/File-Slurp-9999.32.tar.gz
+ provides:
+ File::Slurp 9999.32
+ requirements:
+ B 0
+ Carp 0
+ Errno 0
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ Fcntl 0
+ File::Basename 0
+ File::Spec 3.01
+ File::Temp 0
+ IO::Handle 0
+ POSIX 0
+ strict 0
+ warnings 0
+ File-Which-1.27
+ pathname: P/PL/PLICEASE/File-Which-1.27.tar.gz
+ provides:
+ File::Which 1.27
+ requirements:
+ ExtUtils::MakeMaker 0
+ base 0
+ perl 5.006
+ File-chdir-0.1011
+ pathname: D/DA/DAGOLDEN/File-chdir-0.1011.tar.gz
+ provides:
+ File::chdir 0.1011
+ File::chdir::ARRAY 0.1011
+ File::chdir::SCALAR 0.1011
+ requirements:
+ Carp 0
+ Cwd 3.16
+ Exporter 0
+ ExtUtils::MakeMaker 6.17
+ File::Spec::Functions 3.27
+ perl 5.006
+ strict 0
+ vars 0
+ GIS-Distance-0.20
+ pathname: B/BL/BLUEFEET/GIS-Distance-0.20.tar.gz
+ provides:
+ GIS::Distance 0.20
+ GIS::Distance::ALT 0.20
+ GIS::Distance::Constants 0.20
+ GIS::Distance::Cosine 0.20
+ GIS::Distance::Formula 0.20
+ GIS::Distance::GreatCircle 0.20
+ GIS::Distance::Haversine 0.20
+ GIS::Distance::MathTrig 0.20
+ GIS::Distance::Null 0.20
+ GIS::Distance::Polar 0.20
+ GIS::Distance::Vincenty 0.20
+ requirements:
+ Carp 0
+ Class::Measure::Length 0
+ Const::Fast 0.014
+ Math::Trig 0
+ Module::Build::Tiny 0.035
+ Scalar::Util 0
+ namespace::clean 0.24
+ parent 0
+ perl 5.008001
+ strictures 2.000000
+ GIS-Distance-Fast-0.16
+ pathname: B/BL/BLUEFEET/GIS-Distance-Fast-0.16.tar.gz
+ provides:
+ GIS::Distance::Fast 0.16
+ GIS::Distance::Fast::ALT 0.16
+ GIS::Distance::Fast::Cosine 0.16
+ GIS::Distance::Fast::GreatCircle 0.16
+ GIS::Distance::Fast::Haversine 0.16
+ GIS::Distance::Fast::Null 0.16
+ GIS::Distance::Fast::Polar 0.16
+ GIS::Distance::Fast::Vincenty 0.16
+ requirements:
+ GIS::Distance::Formula 0.17
+ Module::Build::Tiny 0.035
+ namespace::clean 0.24
+ parent 0
+ perl 5.008001
+ strictures 2.000000
+ HTML-Parser-3.83
+ pathname: O/OA/OALDERS/HTML-Parser-3.83.tar.gz
+ provides:
+ HTML::Entities 3.83
+ HTML::Filter 3.83
+ HTML::HeadParser 3.83
+ HTML::LinkExtor 3.83
+ HTML::Parser 3.83
+ HTML::PullParser 3.83
+ HTML::TokeParser 3.83
+ requirements:
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 6.52
+ HTML::Tagset 0
+ HTTP::Headers 0
+ IO::File 0
+ URI 0
+ URI::URL 0
+ XSLoader 0
+ strict 0
+ HTML-Tagset-3.24
+ pathname: P/PE/PETDANCE/HTML-Tagset-3.24.tar.gz
+ provides:
+ HTML::Tagset 3.24
+ requirements:
+ ExtUtils::MakeMaker 6.46
+ perl 5.010001
+ HTTP-Cookies-6.11
+ pathname: O/OA/OALDERS/HTTP-Cookies-6.11.tar.gz
+ provides:
+ HTTP::Cookies 6.11
+ HTTP::Cookies::Microsoft 6.11
+ HTTP::Cookies::Netscape 6.11
+ requirements:
+ Carp 0
+ ExtUtils::MakeMaker 0
+ HTTP::Date 6
+ HTTP::Headers::Util 6
+ HTTP::Request 0
+ locale 0
+ perl 5.008001
+ strict 0
+ HTTP-Date-6.06
+ pathname: O/OA/OALDERS/HTTP-Date-6.06.tar.gz
+ provides:
+ HTTP::Date 6.06
+ requirements:
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ Time::Local 1.28
+ Time::Zone 0
+ perl 5.006002
+ strict 0
+ HTTP-Message-7.00
+ pathname: O/OA/OALDERS/HTTP-Message-7.00.tar.gz
+ provides:
+ HTTP::Config 7.00
+ HTTP::Headers 7.00
+ HTTP::Headers::Auth 7.00
+ HTTP::Headers::ETag 7.00
+ HTTP::Headers::Util 7.00
+ HTTP::Message 7.00
+ HTTP::Request 7.00
+ HTTP::Request::Common 7.00
+ HTTP::Response 7.00
+ HTTP::Status 7.00
+ requirements:
+ Carp 0
+ Clone 0.46
+ Compress::Raw::Bzip2 0
+ Compress::Raw::Zlib 2.062
+ Encode 3.01
+ Encode::Locale 1
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ File::Spec 0
+ HTTP::Date 6
+ IO::Compress::Bzip2 2.021
+ IO::Compress::Deflate 0
+ IO::Compress::Gzip 0
+ IO::HTML 0
+ IO::Uncompress::Inflate 0
+ IO::Uncompress::RawInflate 0
+ LWP::MediaTypes 6
+ MIME::Base64 2.1
+ MIME::QuotedPrint 0
+ URI 1.10
+ parent 0
+ perl 5.008001
+ strict 0
+ warnings 0
+ HTTP-Negotiate-6.01
+ pathname: G/GA/GAAS/HTTP-Negotiate-6.01.tar.gz
+ provides:
+ HTTP::Negotiate 6.01
+ requirements:
+ ExtUtils::MakeMaker 0
+ HTTP::Headers 6
+ perl 5.008001
+ Heap-0.80
+ pathname: J/JM/JMM/Heap-0.80.tar.gz
+ provides:
+ Heap 0.80
+ Heap::Binary 0.80
+ Heap::Binomial 0.80
+ Heap::Elem 0.80
+ Heap::Elem::Num 0.80
+ Heap::Elem::NumRev 0.80
+ Heap::Elem::Ref 0.80
+ Heap::Elem::RefRev 0.80
+ Heap::Elem::Str 0.80
+ Heap::Elem::StrRev 0.80
+ Heap::Fibonacci 0.80
+ requirements:
+ ExtUtils::MakeMaker 0
+ Test::Simple 0.45
+ IO-HTML-1.004
+ pathname: C/CJ/CJM/IO-HTML-1.004.tar.gz
+ provides:
+ IO::HTML 1.004
+ requirements:
+ Carp 0
+ Encode 2.10
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ perl 5.008
+ IO-Socket-SSL-2.095
+ pathname: S/SU/SULLR/IO-Socket-SSL-2.095.tar.gz
+ provides:
+ IO::Socket::SSL 2.095
+ IO::Socket::SSL::Intercept 2.056
+ IO::Socket::SSL::OCSP_Cache 2.095
+ IO::Socket::SSL::OCSP_Resolver 2.095
+ IO::Socket::SSL::PublicSuffix undef
+ IO::Socket::SSL::SSL_Context 2.095
+ IO::Socket::SSL::SSL_HANDLE 2.095
+ IO::Socket::SSL::Session_Cache 2.095
+ IO::Socket::SSL::Trace 2.095
+ 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:
+ IO::String 1.08
+ requirements:
+ ExtUtils::MakeMaker 0
+ JSON-4.10
+ pathname: I/IS/ISHIGAKI/JSON-4.10.tar.gz
+ provides:
+ JSON 4.10
+ JSON::Backend::PP 4.10
+ requirements:
+ ExtUtils::MakeMaker 0
+ Test::More 0
+ JSON-XS-4.03
+ pathname: M/ML/MLEHMANN/JSON-XS-4.03.tar.gz
+ provides:
+ JSON::XS 4.03
+ requirements:
+ Canary::Stability 0
+ ExtUtils::MakeMaker 6.52
+ Types::Serialiser 0
+ common::sense 0
+ LWP-MediaTypes-6.04
+ pathname: O/OA/OALDERS/LWP-MediaTypes-6.04.tar.gz
+ provides:
+ LWP::MediaTypes 6.04
+ requirements:
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ File::Basename 0
+ Scalar::Util 0
+ perl 5.006002
+ strict 0
+ LWP-Protocol-https-6.14
+ pathname: O/OA/OALDERS/LWP-Protocol-https-6.14.tar.gz
+ provides:
+ LWP::Protocol::https 6.14
+ LWP::Protocol::https::Socket 6.14
+ requirements:
+ ExtUtils::MakeMaker 0
+ IO::Socket::SSL 1.970
+ LWP::Protocol::http 0
+ LWP::UserAgent 6.06
+ Net::HTTPS 6
+ base 0
+ perl 5.008001
+ strict 0
+ List-Compare-0.55
+ pathname: J/JK/JKEENAN/List-Compare-0.55.tar.gz
+ provides:
+ List::Compare 0.55
+ List::Compare::Accelerated 0.55
+ List::Compare::Base::_Auxiliary 0.55
+ List::Compare::Base::_Engine 0.55
+ List::Compare::Functional 0.55
+ List::Compare::Multiple 0.55
+ List::Compare::Multiple::Accelerated 0.55
+ requirements:
+ ExtUtils::MakeMaker 0
+ List-MoreUtils-0.430
+ pathname: R/RE/REHSACK/List-MoreUtils-0.430.tar.gz
+ provides:
+ List::MoreUtils 0.430
+ List::MoreUtils::PP 0.430
+ requirements:
+ Exporter::Tiny 0.038
+ ExtUtils::MakeMaker 0
+ List::MoreUtils::XS 0.430
+ List-MoreUtils-XS-0.430
+ pathname: R/RE/REHSACK/List-MoreUtils-XS-0.430.tar.gz
+ provides:
+ List::MoreUtils::XS 0.430
+ requirements:
+ Carp 0
+ ExtUtils::MakeMaker 0
+ File::Basename 0
+ File::Copy 0
+ File::Path 0
+ File::Spec 0
+ IPC::Cmd 0
+ XSLoader 0.22
+ base 0
+ List-UtilsBy-0.12
+ pathname: P/PE/PEVANS/List-UtilsBy-0.12.tar.gz
+ provides:
+ List::UtilsBy 0.12
+ requirements:
+ Exporter 5.57
+ Module::Build 0.4004
+ MIME-Base32-1.303
+ pathname: R/RE/REHSACK/MIME-Base32-1.303.tar.gz
+ provides:
+ MIME::Base32 1.303
+ requirements:
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ perl 5.008001
+ utf8 0
+ MRO-Compat-0.15
+ pathname: H/HA/HAARG/MRO-Compat-0.15.tar.gz
+ provides:
+ MRO::Compat 0.15
+ requirements:
+ ExtUtils::MakeMaker 0
+ perl 5.006
+ Module-Build-0.4234
+ pathname: L/LE/LEONT/Module-Build-0.4234.tar.gz
+ provides:
+ Module::Build 0.4234
+ Module::Build::Base 0.4234
+ Module::Build::Compat 0.4234
+ Module::Build::Config 0.4234
+ Module::Build::Cookbook 0.4234
+ Module::Build::Dumper 0.4234
+ Module::Build::Notes 0.4234
+ Module::Build::PPMMaker 0.4234
+ Module::Build::Platform::Default 0.4234
+ Module::Build::Platform::MacOS 0.4234
+ Module::Build::Platform::Unix 0.4234
+ Module::Build::Platform::VMS 0.4234
+ Module::Build::Platform::VOS 0.4234
+ Module::Build::Platform::Windows 0.4234
+ Module::Build::Platform::aix 0.4234
+ Module::Build::Platform::cygwin 0.4234
+ Module::Build::Platform::darwin 0.4234
+ Module::Build::Platform::os2 0.4234
+ Module::Build::PodParser 0.4234
+ requirements:
+ CPAN::Meta 2.142060
+ Cwd 0
+ Data::Dumper 0
+ ExtUtils::CBuilder 0.27
+ ExtUtils::Install 0
+ ExtUtils::Manifest 0
+ ExtUtils::Mkbootstrap 0
+ ExtUtils::ParseXS 2.21
+ File::Basename 0
+ File::Compare 0
+ File::Copy 0
+ File::Find 0
+ File::Path 0
+ File::Spec 0.82
+ Getopt::Long 0
+ Module::Metadata 1.000002
+ Perl::OSType 1
+ TAP::Harness 3.29
+ Text::Abbrev 0
+ Text::ParseWords 0
+ perl 5.006001
+ version 0.87
+ Module-Build-Tiny-0.052
+ pathname: L/LE/LEONT/Module-Build-Tiny-0.052.tar.gz
+ provides:
+ Module::Build::Tiny 0.052
+ requirements:
+ CPAN::Meta 0
+ DynaLoader 0
+ Exporter 5.57
+ ExtUtils::CBuilder 0
+ ExtUtils::Config 0.003
+ ExtUtils::Helpers 0.020
+ ExtUtils::Install 0
+ ExtUtils::InstallPaths 0.002
+ ExtUtils::ParseXS 0
+ File::Basename 0
+ File::Find 0
+ File::Path 0
+ File::Spec::Functions 0
+ Getopt::Long 2.36
+ JSON::PP 2
+ Pod::Man 0
+ TAP::Harness::Env 0
+ perl 5.006
+ strict 0
+ warnings 0
+ Module-Implementation-0.09
+ pathname: D/DR/DROLSKY/Module-Implementation-0.09.tar.gz
+ provides:
+ Module::Implementation 0.09
+ requirements:
+ Carp 0
+ ExtUtils::MakeMaker 0
+ Module::Runtime 0.012
+ Try::Tiny 0
+ strict 0
+ warnings 0
+ Module-Runtime-0.018
+ pathname: H/HA/HAARG/Module-Runtime-0.018.tar.gz
+ provides:
+ Module::Runtime 0.018
+ requirements:
+ ExtUtils::MakeMaker 0
+ perl 5.006000
+ Mojolicious-9.41
+ pathname: S/SR/SRI/Mojolicious-9.41.tar.gz
+ provides:
+ Mojo undef
+ Mojo::Asset undef
+ Mojo::Asset::File undef
+ Mojo::Asset::Memory undef
+ Mojo::Base undef
+ Mojo::BaseUtil undef
+ Mojo::ByteStream undef
+ Mojo::Cache undef
+ Mojo::Collection undef
+ Mojo::Content undef
+ Mojo::Content::MultiPart undef
+ Mojo::Content::Single undef
+ Mojo::Cookie undef
+ Mojo::Cookie::Request undef
+ Mojo::Cookie::Response undef
+ Mojo::DOM undef
+ Mojo::DOM::CSS undef
+ Mojo::DOM::HTML undef
+ Mojo::Date undef
+ Mojo::DynamicMethods undef
+ Mojo::EventEmitter undef
+ Mojo::Exception undef
+ Mojo::File undef
+ Mojo::Headers undef
+ Mojo::HelloWorld undef
+ Mojo::Home undef
+ Mojo::IOLoop undef
+ Mojo::IOLoop::Client undef
+ Mojo::IOLoop::Server undef
+ Mojo::IOLoop::Stream undef
+ Mojo::IOLoop::Subprocess undef
+ Mojo::IOLoop::TLS undef
+ Mojo::JSON undef
+ Mojo::JSON::Pointer undef
+ Mojo::Loader undef
+ Mojo::Log undef
+ Mojo::Message undef
+ Mojo::Message::Request undef
+ Mojo::Message::Response undef
+ Mojo::Parameters undef
+ Mojo::Path undef
+ Mojo::Promise undef
+ Mojo::Reactor undef
+ Mojo::Reactor::EV undef
+ Mojo::Reactor::Poll undef
+ Mojo::SSE undef
+ Mojo::Server undef
+ Mojo::Server::CGI undef
+ Mojo::Server::Daemon undef
+ Mojo::Server::Hypnotoad undef
+ Mojo::Server::Morbo undef
+ Mojo::Server::Morbo::Backend undef
+ Mojo::Server::Morbo::Backend::Poll undef
+ Mojo::Server::PSGI undef
+ Mojo::Server::Prefork undef
+ Mojo::Template undef
+ Mojo::Transaction undef
+ Mojo::Transaction::HTTP undef
+ Mojo::Transaction::WebSocket undef
+ Mojo::URL undef
+ Mojo::Upload undef
+ Mojo::UserAgent undef
+ Mojo::UserAgent::CookieJar undef
+ Mojo::UserAgent::Proxy undef
+ Mojo::UserAgent::Server undef
+ Mojo::UserAgent::Transactor undef
+ Mojo::Util undef
+ Mojo::WebSocket undef
+ Mojolicious 9.41
+ Mojolicious::Command undef
+ Mojolicious::Command::Author::cpanify undef
+ Mojolicious::Command::Author::generate undef
+ Mojolicious::Command::Author::generate::app undef
+ Mojolicious::Command::Author::generate::dockerfile undef
+ Mojolicious::Command::Author::generate::lite_app undef
+ Mojolicious::Command::Author::generate::makefile undef
+ Mojolicious::Command::Author::generate::plugin undef
+ Mojolicious::Command::Author::inflate undef
+ Mojolicious::Command::cgi undef
+ Mojolicious::Command::daemon undef
+ Mojolicious::Command::eval undef
+ Mojolicious::Command::get undef
+ Mojolicious::Command::prefork undef
+ Mojolicious::Command::psgi undef
+ Mojolicious::Command::routes undef
+ Mojolicious::Command::version undef
+ Mojolicious::Commands undef
+ Mojolicious::Controller undef
+ Mojolicious::Lite undef
+ Mojolicious::Plugin undef
+ Mojolicious::Plugin::Config undef
+ Mojolicious::Plugin::DefaultHelpers undef
+ Mojolicious::Plugin::EPLRenderer undef
+ Mojolicious::Plugin::EPRenderer undef
+ Mojolicious::Plugin::HeaderCondition undef
+ Mojolicious::Plugin::JSONConfig undef
+ Mojolicious::Plugin::Mount undef
+ Mojolicious::Plugin::NotYAMLConfig undef
+ Mojolicious::Plugin::TagHelpers undef
+ Mojolicious::Plugins undef
+ Mojolicious::Renderer undef
+ Mojolicious::Routes undef
+ Mojolicious::Routes::Match undef
+ Mojolicious::Routes::Pattern undef
+ Mojolicious::Routes::Route undef
+ Mojolicious::Sessions undef
+ Mojolicious::Static undef
+ Mojolicious::Types undef
+ Mojolicious::Validator undef
+ Mojolicious::Validator::Validation undef
+ Test::Mojo undef
+ ojo undef
+ requirements:
+ ExtUtils::MakeMaker 0
+ IO::Socket::IP 0.37
+ Sub::Util 1.41
+ perl 5.016
+ Net-HTTP-6.23
+ pathname: O/OA/OALDERS/Net-HTTP-6.23.tar.gz
+ provides:
+ Net::HTTP 6.23
+ Net::HTTP::Methods 6.23
+ Net::HTTP::NB 6.23
+ Net::HTTPS 6.23
+ requirements:
+ Carp 0
+ Compress::Raw::Zlib 0
+ ExtUtils::MakeMaker 0
+ IO::Socket::INET 0
+ IO::Uncompress::Gunzip 0
+ URI 0
+ base 0
+ perl 5.006002
+ strict 0
+ warnings 0
+ Net-SSLeay-1.94
+ pathname: C/CH/CHRISN/Net-SSLeay-1.94.tar.gz
+ provides:
+ Net::SSLeay 1.94
+ Net::SSLeay::Handle 1.94
+ requirements:
+ English 0
+ ExtUtils::MakeMaker 0
+ File::Spec::Functions 0
+ MIME::Base64 0
+ Text::Wrap 0
+ constant 0
+ perl 5.008001
+ Package-Stash-0.40
+ pathname: E/ET/ETHER/Package-Stash-0.40.tar.gz
+ provides:
+ Package::Stash 0.40
+ Package::Stash::PP 0.40
+ requirements:
+ B 0
+ Carp 0
+ Dist::CheckConflicts 0.02
+ ExtUtils::MakeMaker 0
+ Getopt::Long 0
+ Module::Implementation 0.06
+ Package::Stash::XS 0.26
+ Scalar::Util 0
+ Symbol 0
+ Text::ParseWords 0
+ constant 0
+ perl 5.008001
+ strict 0
+ warnings 0
+ Package-Stash-XS-0.30
+ pathname: E/ET/ETHER/Package-Stash-XS-0.30.tar.gz
+ provides:
+ Package::Stash::XS 0.30
+ requirements:
+ ExtUtils::MakeMaker 0
+ XSLoader 0
+ perl 5.008001
+ strict 0
+ warnings 0
+ Params-Util-1.102
+ pathname: R/RE/REHSACK/Params-Util-1.102.tar.gz
+ provides:
+ Params::Util 1.102
+ Params::Util::PP 1.102
+ requirements:
+ Carp 0
+ ExtUtils::MakeMaker 0
+ File::Basename 0
+ File::Copy 0
+ File::Path 0
+ File::Spec 0
+ IPC::Cmd 0
+ 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:
+ Params::ValidationCompiler 0.31
+ Params::ValidationCompiler::Compiler 0.31
+ Params::ValidationCompiler::Exceptions 0.31
+ requirements:
+ B 0
+ Carp 0
+ Eval::Closure 0
+ Exception::Class 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ List::Util 1.29
+ Scalar::Util 0
+ overload 0
+ strict 0
+ warnings 0
+ Path-Tiny-0.150
+ pathname: D/DA/DAGOLDEN/Path-Tiny-0.150.tar.gz
+ provides:
+ Path::Tiny 0.150
+ Path::Tiny::Error 0.150
+ requirements:
+ Carp 0
+ Cwd 0
+ Digest 1.03
+ Digest::SHA 5.45
+ Encode 0
+ Exporter 5.57
+ ExtUtils::MakeMaker 6.17
+ Fcntl 0
+ File::Compare 0
+ File::Copy 0
+ File::Glob 0
+ File::Path 2.07
+ File::Spec 0.86
+ File::Temp 0.19
+ File::stat 0
+ constant 0
+ overload 0
+ perl 5.008001
+ strict 0
+ warnings 0
+ warnings::register 0
+ PkgConfig-0.26026
+ pathname: P/PL/PLICEASE/PkgConfig-0.26026.tar.gz
+ provides:
+ PkgConfig 0.26026
+ requirements:
+ ExtUtils::MakeMaker 6.56
+ Test::More 0.94
+ perl 5.006000
+ Role-Tiny-2.002004
+ pathname: H/HA/HAARG/Role-Tiny-2.002004.tar.gz
+ provides:
+ Role::Tiny 2.002004
+ Role::Tiny::With 2.002004
+ requirements:
+ Exporter 5.57
+ perl 5.006
+ Specio-0.52
+ pathname: D/DR/DROLSKY/Specio-0.52.tar.gz
+ provides:
+ Specio 0.52
+ Specio::Coercion 0.52
+ Specio::Constraint::AnyCan 0.52
+ Specio::Constraint::AnyDoes 0.52
+ Specio::Constraint::AnyIsa 0.52
+ Specio::Constraint::Enum 0.52
+ Specio::Constraint::Intersection 0.52
+ Specio::Constraint::ObjectCan 0.52
+ Specio::Constraint::ObjectDoes 0.52
+ Specio::Constraint::ObjectIsa 0.52
+ Specio::Constraint::Parameterizable 0.52
+ Specio::Constraint::Parameterized 0.52
+ Specio::Constraint::Role::CanType 0.52
+ Specio::Constraint::Role::DoesType 0.52
+ Specio::Constraint::Role::Interface 0.52
+ Specio::Constraint::Role::IsaType 0.52
+ Specio::Constraint::Simple 0.52
+ Specio::Constraint::Structurable 0.52
+ Specio::Constraint::Structured 0.52
+ Specio::Constraint::Union 0.52
+ Specio::Declare 0.52
+ Specio::DeclaredAt 0.52
+ Specio::Exception 0.52
+ Specio::Exporter 0.52
+ Specio::Helpers 0.52
+ Specio::Library::Builtins 0.52
+ Specio::Library::Numeric 0.52
+ Specio::Library::Perl 0.52
+ Specio::Library::String 0.52
+ Specio::Library::Structured 0.52
+ Specio::Library::Structured::Dict 0.52
+ Specio::Library::Structured::Map 0.52
+ Specio::Library::Structured::Tuple 0.52
+ Specio::OO 0.52
+ Specio::PP 0.52
+ Specio::PartialDump 0.52
+ Specio::Registry 0.52
+ Specio::Role::Inlinable 0.52
+ Specio::Subs 0.52
+ Specio::TypeChecks 0.52
+ Specio::XS 0.52
+ Test::Specio 0.52
+ requirements:
+ B 0
+ Carp 0
+ Clone 0
+ Clone::PP 0
+ Devel::StackTrace 0
+ Eval::Closure 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ IO::File 0
+ List::Util 1.33
+ MRO::Compat 0
+ Module::Implementation 0
+ Module::Runtime 0
+ Role::Tiny 1.003003
+ Role::Tiny::With 0
+ Scalar::Util 0
+ Sub::Quote 0
+ Test::Fatal 0
+ Test::More 0.96
+ Try::Tiny 0
+ XString 0
+ overload 0
+ parent 0
+ perl 5.008
+ re 0
+ strict 0
+ version 0.83
+ warnings 0
+ Sub-Exporter-0.991
+ pathname: R/RJ/RJBS/Sub-Exporter-0.991.tar.gz
+ provides:
+ Sub::Exporter 0.991
+ Sub::Exporter::Util 0.991
+ requirements:
+ Carp 0
+ Data::OptList 0.100
+ ExtUtils::MakeMaker 6.78
+ Params::Util 0.14
+ Sub::Install 0.92
+ perl 5.012
+ strict 0
+ warnings 0
+ Sub-Exporter-Progressive-0.001013
+ pathname: F/FR/FREW/Sub-Exporter-Progressive-0.001013.tar.gz
+ provides:
+ Sub::Exporter::Progressive 0.001013
+ requirements:
+ ExtUtils::MakeMaker 0
+ Sub-Install-0.929
+ pathname: R/RJ/RJBS/Sub-Install-0.929.tar.gz
+ provides:
+ Sub::Install 0.929
+ requirements:
+ B 0
+ Carp 0
+ ExtUtils::MakeMaker 6.78
+ Scalar::Util 0
+ perl 5.008000
+ strict 0
+ warnings 0
+ Sub-Quote-2.006009
+ pathname: H/HA/HAARG/Sub-Quote-2.006009.tar.gz
+ provides:
+ Sub::Defer 2.006009
+ Sub::Quote 2.006009
+ requirements:
+ ExtUtils::MakeMaker 0
+ Scalar::Util 0
+ perl 5.006
+ Test-Compile-v3.3.3
+ pathname: E/EG/EGILES/Test-Compile-v3.3.3.tar.gz
+ provides:
+ Test::Compile v3.3.3
+ Test::Compile::Internal v3.3.3
+ requirements:
+ Exporter 5.68
+ Module::Build 0.38
+ parent 0.225
+ perl v5.10.0
+ Test-Fatal-0.017
+ pathname: R/RJ/RJBS/Test-Fatal-0.017.tar.gz
+ provides:
+ Test::Fatal 0.017
+ requirements:
+ Carp 0
+ Exporter 5.57
+ ExtUtils::MakeMaker 6.78
+ Test::Builder 0
+ Try::Tiny 0.07
+ strict 0
+ warnings 0
+ Test-Number-Delta-1.06
+ pathname: D/DA/DAGOLDEN/Test-Number-Delta-1.06.tar.gz
+ provides:
+ Test::Number::Delta 1.06
+ requirements:
+ Carp 0
+ Exporter 0
+ ExtUtils::MakeMaker 6.17
+ Test::Builder 0
+ perl 5.006
+ strict 0
+ vars 0
+ warnings 0
+ Test-Pod-1.52
+ pathname: E/ET/ETHER/Test-Pod-1.52.tar.gz
+ provides:
+ Test::Pod 1.52
+ requirements:
+ ExtUtils::MakeMaker 0
+ File::Find 0
+ Pod::Simple 3.05
+ Test::Builder::Tester 1.02
+ Test::More 0.62
+ perl 5.008
+ Text-CSV-2.06
+ pathname: I/IS/ISHIGAKI/Text-CSV-2.06.tar.gz
+ provides:
+ Text::CSV 2.06
+ Text::CSV::ErrorDiag 2.06
+ Text::CSV_PP 2.06
+ requirements:
+ ExtUtils::MakeMaker 0
+ IO::Handle 0
+ Test::Harness 0
+ Test::More 0.92
+ perl 5.006001
+ Text-LevenshteinXS-0.03
+ pathname: J/JG/JGOLDBERG/Text-LevenshteinXS-0.03.tar.gz
+ provides:
+ Text::LevenshteinXS 0.03
+ requirements:
+ ExtUtils::MakeMaker 0
+ Test 0
+ TimeDate-2.33
+ pathname: A/AT/ATOOMIC/TimeDate-2.33.tar.gz
+ provides:
+ Date::Format 2.24
+ Date::Format::Generic 2.24
+ Date::Language 1.10
+ Date::Language::Afar 0.99
+ Date::Language::Amharic 1.00
+ Date::Language::Austrian 1.01
+ Date::Language::Brazilian 1.01
+ Date::Language::Bulgarian 1.01
+ Date::Language::Chinese 1.00
+ Date::Language::Chinese_GB 1.01
+ Date::Language::Czech 1.01
+ Date::Language::Danish 1.01
+ Date::Language::Dutch 1.02
+ Date::Language::English 1.01
+ Date::Language::Finnish 1.01
+ Date::Language::French 1.04
+ Date::Language::Gedeo 0.99
+ Date::Language::German 1.02
+ Date::Language::Greek 1.00
+ Date::Language::Hungarian 1.01
+ Date::Language::Icelandic 1.01
+ Date::Language::Italian 1.01
+ Date::Language::Norwegian 1.01
+ Date::Language::Occitan 1.04
+ Date::Language::Oromo 0.99
+ Date::Language::Romanian 1.01
+ Date::Language::Russian 1.01
+ Date::Language::Russian_cp1251 1.01
+ Date::Language::Russian_koi8r 1.01
+ Date::Language::Sidama 0.99
+ Date::Language::Somali 0.99
+ Date::Language::Spanish 1.00
+ Date::Language::Swedish 1.01
+ Date::Language::Tigrinya 1.00
+ Date::Language::TigrinyaEritrean 1.00
+ Date::Language::TigrinyaEthiopian 1.00
+ Date::Language::Turkish 1.0
+ Date::Parse 2.33
+ Time::Zone 2.24
+ TimeDate 1.21
+ requirements:
+ ExtUtils::MakeMaker 0
+ Travel-Status-DE-DBRIS-0.13
+ pathname: D/DE/DERF/Travel-Status-DE-DBRIS-0.13.tar.gz
+ provides:
+ Travel::Status::DE::DBRIS 0.13
+ Travel::Status::DE::DBRIS::Formation 0.13
+ Travel::Status::DE::DBRIS::Formation::Carriage 0.13
+ Travel::Status::DE::DBRIS::Formation::Group 0.13
+ Travel::Status::DE::DBRIS::Formation::Sector 0.13
+ Travel::Status::DE::DBRIS::Journey 0.13
+ Travel::Status::DE::DBRIS::JourneyAtStop 0.13
+ Travel::Status::DE::DBRIS::Location 0.13
+ requirements:
+ Carp 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
+ perl v5.20.0
+ Travel-Status-DE-HAFAS-6.22
+ pathname: D/DE/DERF/Travel-Status-DE-HAFAS-6.22.tar.gz
+ provides:
+ Travel::Status::DE::HAFAS 6.22
+ Travel::Status::DE::HAFAS::Journey 6.22
+ Travel::Status::DE::HAFAS::Location 6.22
+ Travel::Status::DE::HAFAS::Message 6.22
+ Travel::Status::DE::HAFAS::Polyline 6.22
+ Travel::Status::DE::HAFAS::Product 6.22
+ Travel::Status::DE::HAFAS::Services 6.22
+ Travel::Status::DE::HAFAS::Stop 6.22
+ Travel::Status::DE::HAFAS::StopFinder 6.22
+ requirements:
+ Carp 0
+ Class::Accessor 0.16
+ DateTime 0
+ DateTime::Format::Strptime 0
+ Digest::MD5 0
+ Getopt::Long 0
+ JSON 0
+ LWP::Protocol::https 0
+ LWP::UserAgent 0
+ List::MoreUtils 0
+ List::Util 0
+ Module::Build 0.4
+ Test::Compile 0
+ Test::More 0
+ Test::Pod 0
+ perl v5.14.0
+ Travel-Status-DE-IRIS-1.98
+ pathname: D/DE/DERF/Travel-Status-DE-IRIS-1.98.tar.gz
+ provides:
+ Travel::Status::DE::IRIS 1.98
+ Travel::Status::DE::IRIS::Result 1.98
+ Travel::Status::DE::IRIS::Stations 1.98
+ requirements:
+ Carp 0
+ Class::Accessor 0
+ DateTime 0
+ DateTime::Format::Strptime 0
+ Encode 0
+ File::Slurp 9999.19
+ GIS::Distance 0
+ Getopt::Long 0
+ JSON 0
+ LWP::Protocol::https 0
+ LWP::UserAgent 0
+ List::Compare 0.29
+ List::MoreUtils 0
+ List::Util 0
+ List::UtilsBy 0
+ Module::Build 0.4
+ Test::Compile 0
+ Test::Fatal 0
+ Test::More 0
+ Test::Number::Delta 0
+ Test::Pod 0
+ Text::CSV 0
+ Text::LevenshteinXS 0
+ XML::LibXML 0
+ perl v5.14.2
+ Travel-Status-DE-VRR-3.14
+ pathname: D/DE/DERF/Travel-Status-DE-VRR-3.14.tar.gz
+ provides:
+ Travel::Status::DE::EFA 3.14
+ Travel::Status::DE::EFA::Departure 3.14
+ Travel::Status::DE::EFA::Info 3.14
+ Travel::Status::DE::EFA::Line 3.14
+ Travel::Status::DE::EFA::Services 3.14
+ Travel::Status::DE::EFA::Stop 3.14
+ Travel::Status::DE::EFA::Trip 3.14
+ Travel::Status::DE::VRR 3.14
+ requirements:
+ Carp 0
+ Class::Accessor 0
+ DateTime 0
+ DateTime::Format::Strptime 0
+ File::Slurp 0
+ Getopt::Long 0
+ JSON 0
+ LWP::Protocol::https 0
+ LWP::UserAgent 0
+ List::Util 0
+ Module::Build 0.4
+ Test::More 0
+ perl v5.10.1
+ Travel-Status-MOTIS-0.03
+ pathname: D/DE/DERF/Travel-Status-MOTIS-0.03.tar.gz
+ provides:
+ Travel::Status::MOTIS 0.03
+ Travel::Status::MOTIS::Polyline 0.03
+ Travel::Status::MOTIS::Services 0.03
+ Travel::Status::MOTIS::Stop 0.03
+ Travel::Status::MOTIS::Stopover 0.03
+ Travel::Status::MOTIS::Trip 0.03
+ Travel::Status::MOTIS::TripAtStopover 0.03
+ 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:
+ Try::Tiny 0.32
+ requirements:
+ Carp 0
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ constant 0
+ perl 5.006
+ strict 0
+ warnings 0
+ Types-Serialiser-1.01
+ pathname: M/ML/MLEHMANN/Types-Serialiser-1.01.tar.gz
+ provides:
+ JSON::PP::Boolean 1.01
+ Types::Serialiser 1.01
+ Types::Serialiser::BooleanBase 1.01
+ Types::Serialiser::Error 1.01
+ requirements:
+ ExtUtils::MakeMaker 0
+ common::sense 0
+ 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
+ Data::Dumper 0
+ Encode 0
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ MIME::Base32 0
+ MIME::Base64 2
+ Net::Domain 0
+ Scalar::Util 0
+ constant 0
+ integer 0
+ overload 0
+ parent 0
+ perl 5.008001
+ strict 0
+ utf8 0
+ warnings 0
+ Variable-Magic-0.64
+ pathname: V/VP/VPIT/Variable-Magic-0.64.tar.gz
+ provides:
+ Variable::Magic 0.64
+ requirements:
+ Carp 0
+ Config 0
+ Exporter 0
+ ExtUtils::MakeMaker 0
+ IO::Handle 0
+ IO::Select 0
+ IPC::Open3 0
+ POSIX 0
+ Socket 0
+ Test::More 0
+ XSLoader 0
+ base 0
+ lib 0
+ perl 5.008
+ WWW-RobotRules-6.02
+ pathname: G/GA/GAAS/WWW-RobotRules-6.02.tar.gz
+ provides:
+ WWW::RobotRules 6.02
+ WWW::RobotRules::AnyDBM_File 6.00
+ WWW::RobotRules::InCore 6.02
+ requirements:
+ AnyDBM_File 0
+ ExtUtils::MakeMaker 0
+ Fcntl 0
+ URI 1.10
+ perl 5.008001
+ XML-LibXML-2.0210
+ pathname: S/SH/SHLOMIF/XML-LibXML-2.0210.tar.gz
+ provides:
+ XML::LibXML 2.0210
+ XML::LibXML::Attr 2.0210
+ XML::LibXML::AttributeHash 2.0210
+ XML::LibXML::Boolean 2.0210
+ XML::LibXML::CDATASection 2.0210
+ XML::LibXML::Comment 2.0210
+ XML::LibXML::Common 2.0210
+ XML::LibXML::Devel 2.0210
+ XML::LibXML::Document 2.0210
+ XML::LibXML::DocumentFragment 2.0210
+ XML::LibXML::Dtd 2.0210
+ XML::LibXML::Element 2.0210
+ XML::LibXML::ErrNo 2.0210
+ XML::LibXML::Error 2.0210
+ XML::LibXML::InputCallback 2.0210
+ XML::LibXML::Literal 2.0210
+ XML::LibXML::NamedNodeMap 2.0210
+ XML::LibXML::Namespace 2.0210
+ XML::LibXML::Node 2.0210
+ XML::LibXML::NodeList 2.0210
+ XML::LibXML::Number 2.0210
+ XML::LibXML::PI 2.0210
+ XML::LibXML::Pattern 2.0210
+ XML::LibXML::Reader 2.0210
+ XML::LibXML::RegExp 2.0210
+ XML::LibXML::RelaxNG 2.0210
+ XML::LibXML::SAX 2.0210
+ XML::LibXML::SAX::AttributeNode 2.0210
+ XML::LibXML::SAX::Builder 2.0210
+ XML::LibXML::SAX::Generator 2.0210
+ XML::LibXML::SAX::Parser 2.0210
+ XML::LibXML::Schema 2.0210
+ XML::LibXML::Text 2.0210
+ XML::LibXML::XPathContext 2.0210
+ XML::LibXML::XPathExpression 2.0210
+ XML::LibXML::_SAXParser 2.0210
+ requirements:
+ Alien::Base::Wrapper 0
+ Alien::Libxml2 0.14
+ Carp 0
+ Config 0
+ DynaLoader 0
+ Encode 0
+ Exporter 5.57
+ ExtUtils::MakeMaker 0
+ IO::Handle 0
+ Scalar::Util 0
+ Tie::Hash 0
+ XML::NamespaceSupport 1.07
+ XML::SAX 0.11
+ XML::SAX::Base 0
+ XML::SAX::DocumentLocator 0
+ XML::SAX::Exception 0
+ base 0
+ constant 0
+ overload 0
+ parent 0
+ perl 5.008001
+ strict 0
+ vars 0
+ warnings 0
+ XML-NamespaceSupport-1.12
+ pathname: P/PE/PERIGRIN/XML-NamespaceSupport-1.12.tar.gz
+ provides:
+ XML::NamespaceSupport 1.12
+ requirements:
+ ExtUtils::MakeMaker 6.17
+ constant 0
+ perl 5.006
+ strict 0
+ vars 0
+ warnings 0
+ XML-SAX-1.02
+ pathname: G/GR/GRANTM/XML-SAX-1.02.tar.gz
+ provides:
+ XML::SAX 1.02
+ XML::SAX::DocumentLocator undef
+ XML::SAX::ParserFactory 1.02
+ XML::SAX::PurePerl 1.02
+ XML::SAX::PurePerl::DebugHandler undef
+ XML::SAX::PurePerl::Exception undef
+ XML::SAX::PurePerl::Productions undef
+ XML::SAX::PurePerl::Reader undef
+ XML::SAX::PurePerl::Reader::Stream undef
+ XML::SAX::PurePerl::Reader::String undef
+ XML::SAX::PurePerl::Reader::URI undef
+ requirements:
+ ExtUtils::MakeMaker 0
+ File::Temp 0
+ XML::NamespaceSupport 0.03
+ XML::SAX::Base 1.05
+ XML-SAX-Base-1.09
+ pathname: G/GR/GRANTM/XML-SAX-Base-1.09.tar.gz
+ provides:
+ XML::SAX::Base 1.09
+ XML::SAX::Base::NoHandler 1.09
+ XML::SAX::Exception 1.09
+ requirements:
+ ExtUtils::MakeMaker 0
+ perl 5.008
+ XString-0.005
+ pathname: A/AT/ATOOMIC/XString-0.005.tar.gz
+ provides:
+ XString 0.005
+ requirements:
+ ExtUtils::MakeMaker 0
+ perl 5.008
+ bareword-filehandles-0.007
+ pathname: I/IL/ILMARI/bareword-filehandles-0.007.tar.gz
+ provides:
+ bareword::filehandles 0.007
+ requirements:
+ B::Hooks::OP::Check 0
+ ExtUtils::Depends 0
+ ExtUtils::MakeMaker 0
+ Test::More 0.88
+ XSLoader 0
+ if 0
+ perl 5.008001
+ strict 0
+ warnings 0
+ common-sense-3.75
+ pathname: M/ML/MLEHMANN/common-sense-3.75.tar.gz
+ provides:
+ common::sense 3.75
+ requirements:
+ ExtUtils::MakeMaker 0
+ indirect-0.39
+ pathname: V/VP/VPIT/indirect-0.39.tar.gz
+ provides:
+ indirect 0.39
+ requirements:
+ Carp 0
+ Config 0
+ ExtUtils::MakeMaker 0
+ File::Spec 0
+ IO::Handle 0
+ IO::Select 0
+ IPC::Open3 0
+ POSIX 0
+ Socket 0
+ Test::More 0
+ XSLoader 0
+ lib 0
+ perl 5.008001
+ libwww-perl-6.79
+ pathname: O/OA/OALDERS/libwww-perl-6.79.tar.gz
+ provides:
+ LWP 6.79
+ LWP::Authen::Basic 6.79
+ LWP::Authen::Digest 6.79
+ LWP::Authen::Ntlm 6.79
+ LWP::ConnCache 6.79
+ LWP::Debug 6.79
+ LWP::Debug::TraceHTTP 6.79
+ LWP::DebugFile 6.79
+ LWP::MemberMixin 6.79
+ LWP::Protocol 6.79
+ LWP::Protocol::cpan 6.79
+ LWP::Protocol::data 6.79
+ LWP::Protocol::file 6.79
+ LWP::Protocol::ftp 6.79
+ LWP::Protocol::gopher 6.79
+ LWP::Protocol::http 6.79
+ LWP::Protocol::loopback 6.79
+ LWP::Protocol::mailto 6.79
+ LWP::Protocol::nntp 6.79
+ LWP::Protocol::nogo 6.79
+ LWP::RobotUA 6.79
+ LWP::Simple 6.79
+ LWP::UserAgent 6.79
+ requirements:
+ Digest::MD5 0
+ Encode 2.12
+ Encode::Locale 0
+ ExtUtils::MakeMaker 0
+ File::Copy 0
+ File::Listing 6
+ File::Temp 0
+ Getopt::Long 0
+ HTML::Entities 0
+ HTML::HeadParser 3.71
+ HTTP::Cookies 6
+ HTTP::Date 6
+ HTTP::Negotiate 6
+ HTTP::Request 6.18
+ HTTP::Request::Common 6.18
+ HTTP::Response 6.18
+ HTTP::Status 6.18
+ IO::Select 0
+ IO::Socket 0
+ LWP::MediaTypes 6
+ MIME::Base64 2.1
+ Module::Load 0
+ Net::FTP 2.58
+ Net::HTTP 6.18
+ Scalar::Util 0
+ Try::Tiny 0
+ URI 1.10
+ URI::Escape 0
+ WWW::RobotRules 6
+ parent 0.217
+ perl 5.008001
+ strict 0
+ warnings 0
+ multidimensional-0.014
+ pathname: I/IL/ILMARI/multidimensional-0.014.tar.gz
+ provides:
+ multidimensional 0.014
+ requirements:
+ B::Hooks::OP::Check 0.19
+ CPAN::Meta 2.112580
+ ExtUtils::Depends 0
+ ExtUtils::MakeMaker 0
+ Test::More 0.88
+ XSLoader 0
+ if 0
+ perl 5.008001
+ strict 0
+ warnings 0
+ namespace-autoclean-0.31
+ pathname: E/ET/ETHER/namespace-autoclean-0.31.tar.gz
+ provides:
+ namespace::autoclean 0.31
+ requirements:
+ B 0
+ B::Hooks::EndOfScope 0.12
+ ExtUtils::MakeMaker 0
+ List::Util 0
+ namespace::clean 0.20
+ perl 5.006
+ strict 0
+ warnings 0
+ namespace-clean-0.27
+ pathname: R/RI/RIBASUSHI/namespace-clean-0.27.tar.gz
+ provides:
+ namespace::clean 0.27
+ requirements:
+ B::Hooks::EndOfScope 0.12
+ ExtUtils::MakeMaker 0
+ Package::Stash 0.23
+ perl 5.008001
+ strictures-2.000006
+ pathname: H/HA/HAARG/strictures-2.000006.tar.gz
+ provides:
+ strictures 2.000006
+ strictures::extra undef
+ requirements:
+ bareword::filehandles 0
+ indirect 0
+ multidimensional 0
+ perl 5.006
diff --git a/examples/db-infoscreen.service b/examples/db-infoscreen.service
new file mode 100644
index 0000000..2ecc577
--- /dev/null
+++ b/examples/db-infoscreen.service
@@ -0,0 +1,23 @@
+[Unit]
+Description=dbf.finalrewind.org
+After=network.target
+
+[Service]
+Type=simple
+RemainAfterExit=yes
+PIDFile=/tmp/db-fakedisplay.pid
+ExecStart=/usr/bin/hypnotoad -f index.pl
+ExecStop=/usr/bin/hypnotoad -s index.pl
+ExecReload=/usr/bin/hypnotoad index.pl
+User=db-fakedisplay
+WorkingDirectory=/srv/www/db-fakedisplay
+Environment=LANG=en_US.UTF-8
+Environment=DBFAKEDISPLAY_LISTEN=http://127.0.0.1:8092
+Environment=DBFAKEDISPLAY_WORKERS=4
+Environment=DBFAKEDISPLAY_STATS=/tmp/dbf-api-stats
+Environment=DBFAKEDISPLAY_HAFAS_CACHE=/var/cache/dbf/hafas
+Environment=DBFAKEDISPLAY_IRIS_CACHE=/var/cache/dbf/iris
+Environment=DBFAKEDISPLAY_IRISRT_CACHE=/var/cache/dbf/iris-rt
+
+[Install]
+WantedBy=multi-user.target
diff --git a/examples/dbf_update_zugbildungsplan b/examples/dbf_update_zugbildungsplan
new file mode 100644
index 0000000..1cd82e7
--- /dev/null
+++ b/examples/dbf_update_zugbildungsplan
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -eu
+
+wget -qO share/zugbildungsplan.json.new https://lib.finalrewind.org/dbdb/db_zugbildung_v1.json
+
+chmod 644 share/zugbildungsplan.json.new
+
+mv share/zugbildungsplan.json.new share/zugbildungsplan.json
diff --git a/examples/imprint.html.ep b/examples/imprint.html.ep
new file mode 100644
index 0000000..f571abf
--- /dev/null
+++ b/examples/imprint.html.ep
@@ -0,0 +1,7 @@
+<div class="container">
+<h1>Impressum</h1>
+Verantwortlich für diese Seite ist:<br/>
+Vorname Nachname<br/>
+Straße Hausnummer<br/>
+PLZ Ort
+</div>
diff --git a/examples/nginx-cache.conf b/examples/nginx-cache.conf
new file mode 100644
index 0000000..bea4366
--- /dev/null
+++ b/examples/nginx-cache.conf
@@ -0,0 +1,2 @@
+proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=dbf_cache:10m max_size=1g inactive=10m use_temp_path=off;
+proxy_cache_valid any 1m;
diff --git a/examples/nginx-site.conf b/examples/nginx-site.conf
new file mode 100644
index 0000000..1aac763
--- /dev/null
+++ b/examples/nginx-site.conf
@@ -0,0 +1,32 @@
+upstream dbf {
+ server 127.0.0.1:8092;
+}
+
+server {
+ listen [::]:443;
+ listen 0.0.0.0:443;
+
+ server_name FIXME;
+ access_log /var/log/nginx/FIXME.log anonip;
+
+ add_header Strict-Transport-Security "max-age=31536000" always;
+
+ location /static {
+ expires 1y;
+ root /srv/www/dbf/public;
+ }
+
+ location / {
+ proxy_cache dbf_cache;
+ proxy_cache_lock on;
+ proxy_ignore_headers "Set-Cookie";
+ proxy_hide_header "Set-Cookie";
+ proxy_pass http://dbf;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto "http";
+ add_header X-Frame-Options "";
+ add_header Strict-Transport-Security "max-age=31536000" always;
+ }
+}
diff --git a/examples/privacy.html.ep b/examples/privacy.html.ep
new file mode 100644
index 0000000..65380d4
--- /dev/null
+++ b/examples/privacy.html.ep
@@ -0,0 +1,34 @@
+<div class="container">
+<h1>Datenschutzerklärung</h1>
+<p>
+Bei jedem Aufruf dieser Website werden vom Webserver die folgenden Daten
+gespeichert und für FIXME aufbewahrt:
+</p>
+
+<p>
+<ul>
+ <li>...</li>
+</ul>
+</p>
+
+<p>
+Die bei Nutzung von „Bahnhöfe im Umfeld suchen“ an den
+Server übertragenen Geokoordinaten werden nicht gespeichert.
+</p>
+
+<p>
+Bei Aktivierung der Schaltfläche „Ausgewählte Optionen als Default speichern“
+werden Cookies verwendet, um die ausgewählten Optionen im Browser abzuspeichern
+und bei späteren Seitenaufrufen wiederherzustellen. Die Cookies dienen
+ausschließlich diesem Zweck und werden nicht zur Identifikation oder Verfolgung
+(Tracking) von Personen verwendet.
+</p>
+
+<p>
+Jede Person hat das Recht, Auskunft über die zu ihr
+gespeicherten Daten zu erhalten, fehlerhafte Daten zu berichtigen und (soweit
+nicht durch andere Gesetze eingeschränkt) die Löschung gespeicherter
+personenbezogener Daten zu verlangen. Derartige Anfragen können an
+FIXME gerichtet werden.
+</p>
+</div>
diff --git a/index.pl b/index.pl
index fb01343..467e0b2 100644
--- a/index.pl
+++ b/index.pl
@@ -1,790 +1,12 @@
#!/usr/bin/env perl
-use Mojolicious::Lite;
-use Cache::File;
-use File::Slurp qw(read_file write_file);
-use List::MoreUtils qw();
-use Travel::Status::DE::HAFAS;
-use Travel::Status::DE::HAFAS::StopFinder;
-use Travel::Status::DE::IRIS;
-use Travel::Status::DE::IRIS::Stations;
-use 5.014;
-use utf8;
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
-no if $] >= 5.018, warnings => 'experimental::smartmatch';
+use strict;
+use warnings;
-our $VERSION = qx{git describe --dirty} || '0.05';
+use lib 'lib';
+use Mojolicious::Commands;
-my $refresh_interval = 180;
-
-sub log_api_access {
- my $counter = 1;
- if ( -r $ENV{DBFAKEDISPLAY_STATS} ) {
- $counter = read_file( $ENV{DBFAKEDISPLAY_STATS} ) + 1;
- }
- write_file( $ENV{DBFAKEDISPLAY_STATS}, $counter );
- return;
-}
-
-sub get_results_for {
- my ( $backend, $station, %opt ) = @_;
-
- my $cache = Cache::File->new(
- cache_root => '/tmp/db-fakedisplay',
- default_expires => $refresh_interval . ' sec',
- lock_level => Cache::File::LOCK_LOCAL(),
- );
-
- # Cache::File has UTF-8 problems, so strip it (and any other potentially
- # problematic chars).
- my $cstation = $station;
- $cstation =~ tr{[0-9a-zA-Z -]}{}cd;
-
- my $cache_str = "${backend}_${cstation}";
-
- my $data = $cache->thaw($cache_str);
-
- if ( not $data ) {
- if ( $ENV{DBFAKEDISPLAY_STATS} ) {
- log_api_access();
- }
- if ( $backend eq 'iris' ) {
-
- # requests with DS100 codes should be preferred (they avoid
- # encoding problems on the IRIS server). However, only use them
- # if we have an exact match. Ask the backend otherwise.
- my @station_matches
- = Travel::Status::DE::IRIS::Stations::get_station($station);
- if ( @station_matches == 1 ) {
- $station = $station_matches[0][0];
- }
-
- my $status = Travel::Status::DE::IRIS->new(
- station => $station,
- serializable => 1,
- %opt
- );
- $data = {
- results => [ $status->results ],
- errstr => $status->errstr,
- };
- $cache->freeze( $cache_str, $data );
- }
- elsif ( $backend eq 'ris' ) {
- my $status = Travel::Status::DE::HAFAS->new(
- station => $station,
- excluded_mots => [qw[bus ferry ondemand tram u]],
- %opt
- );
- $data = {
- results => [ $status->results ],
- errstr => $status->errstr,
- };
- $cache->freeze( $cache_str, $data );
- }
- else {
- $data = {
- results => [],
- errstr => "Backend '$backend' not supported",
- };
- }
- }
-
- return $data;
-}
-
-helper 'handle_no_results' => sub {
- my ( $self, $backend, $station, $errstr ) = @_;
-
- if ( $backend eq 'ris' ) {
- my $db_service = Travel::Status::DE::HAFAS::get_service('DB');
- my $sf = Travel::Status::DE::HAFAS::StopFinder->new(
- url => $db_service->{stopfinder},
- input => $station,
- );
- my @candidates
- = map { [ $_->{name}, $_->{id} ] } $sf->results;
- if ( @candidates > 1
- or ( @candidates == 1 and $candidates[0][1] ne $station ) )
- {
- $self->render(
- 'landingpage',
- stationlist => \@candidates,
- hide_opts => 0
- );
- return;
- }
- }
- if ( $backend eq 'iris' ) {
- my @candidates = map { [ $_->[1], $_->[0] ] }
- Travel::Status::DE::IRIS::Stations::get_station($station);
- if ( @candidates > 1
- or ( @candidates == 1 and $candidates[0][1] ne $station ) )
- {
- $self->render(
- 'landingpage',
- stationlist => \@candidates,
- hide_opts => 0
- );
- return;
- }
- }
- $self->render(
- 'landingpage',
- error => ( $errstr // "Got no results for '$station'" ),
- hide_opts => 0
- );
- return;
-};
-
-helper 'handle_no_results_marudor' => sub {
- my ( $self, $backend, $station, $errstr, $api_version, $callback ) = @_;
-
- $self->res->headers->access_control_allow_origin(q{*});
- my $json;
- if ($errstr) {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => $errstr,
- }
- );
- }
- elsif ( $backend eq 'iris' ) {
- my @candidates = map { { code => $_->[0], name => $_->[1] } }
- Travel::Status::DE::IRIS::Stations::get_station($station);
- if ( @candidates > 1
- or ( @candidates == 1 and $candidates[0]{code} ne $station ) )
- {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => 'ambiguous station code/name',
- candidates => \@candidates,
- }
- );
- }
- else {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => ( $errstr // "Got no results for '$station'" )
- }
- );
- }
- }
- else {
- $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- version => $VERSION,
- error => ( $errstr // 'unknown station code/name' )
- }
- );
- }
- if ($callback) {
- $self->render(
- data => "$callback($json);",
- format => 'json'
- );
- }
- else {
- $self->render(
- data => $json,
- format => 'json'
- );
- }
- return;
-};
-
-helper 'is_important' => sub {
- my ( $self, $stop ) = @_;
-
- if ( $stop =~ m{ Hbf | Flughafen }ox ) {
- return 1;
- }
- return;
-};
-
-helper 'json_route_diff' => sub {
- my ( $self, $route, $sched_route ) = @_;
- my @json_route;
- my @route = @{$route};
- my @sched_route = @{$sched_route};
-
- my $route_idx = 0;
- my $sched_idx = 0;
-
- while ( $route_idx <= $#route and $sched_idx <= $#sched_route ) {
- if ( $route[$route_idx] eq $sched_route[$sched_idx] ) {
- push( @json_route, { name => $route[$route_idx] } );
- $route_idx++;
- $sched_idx++;
- }
-
- # this branch is inefficient, but won't be taken frequently
- elsif ( not( $route[$route_idx] ~~ \@sched_route ) ) {
- push(
- @json_route,
- {
- name => $route[$route_idx],
- isAdditional => 1
- }
- );
- $route_idx++;
- }
- else {
- push(
- @json_route,
- {
- name => $sched_route[$sched_idx],
- isCancelled => 1
- }
- );
- $sched_idx++;
- }
- }
- while ( $route_idx < $#route ) {
- push(
- @json_route,
- {
- name => $route[$route_idx],
- isAdditional => 1,
- isCancelled => 0
- }
- );
- $route_idx++;
- }
- while ( $sched_idx < $#sched_route ) {
- push(
- @json_route,
- {
- name => $sched_route[$sched_idx],
- isAdditional => 0,
- isCancelled => 1
- }
- );
- $sched_idx++;
- }
- return @json_route;
-};
-
-sub handle_request {
- my $self = shift;
- my $station = $self->stash('station');
- my $via = $self->stash('via');
-
- my @platforms = split( /,/, $self->param('platforms') // q{} );
- my @lines = split( /,/, $self->param('lines') // q{} );
- my $template = $self->param('mode') // 'clean';
- my $hide_low_delay = $self->param('hidelowdelay') // 0;
- my $hide_opts = $self->param('hide_opts') // 0;
- my $show_realtime = $self->param('show_realtime') // 0;
- my $backend = $self->param('backend') // 'iris';
- my $admode = $self->param('admode') // 'deparr';
- my $callback = $self->param('callback');
- my $apiver = $self->param('version') // 0;
- my %opt;
-
- my $api_version
- = $backend eq 'iris'
- ? $Travel::Status::DE::IRIS::VERSION
- : $Travel::Status::DE::HAFAS::VERSION;
-
- $self->stash( departures => [] );
- $self->stash( title => 'db-infoscreen' );
- $self->stash( version => $VERSION );
-
- if ( not( $template ~~ [qw[clean json marudor multi single]] ) ) {
- $template = 'clean';
- }
-
- if ( not $station ) {
- $self->render(
- 'landingpage',
- hide_opts => 0,
- show_intro => 1
- );
- return;
- }
-
- if ( $template eq 'marudor' and $backend eq 'iris' ) {
- $opt{lookahead} = 120;
- }
-
- my @departures;
- my $data = get_results_for( $backend, $station, %opt );
- my $results_ref = $data->{results};
- my $errstr = $data->{errstr};
- my @results = @{$results_ref};
-
- if ( not @results and $template ~~ [qw[json marudor]] ) {
- $self->handle_no_results_marudor( $backend, $station, $errstr,
- $api_version, $callback );
- return;
- }
-
- if ( not @results ) {
- $self->handle_no_results( $backend, $station, $errstr );
- return;
- }
-
- if ( $template eq 'single' ) {
- if ( not @platforms ) {
- for my $result (@results) {
- if ( not( $result->platform ~~ \@platforms ) ) {
- push( @platforms, $result->platform );
- }
- }
- @platforms = sort { $a <=> $b } @platforms;
- }
- my %pcnt;
- @results = grep { $pcnt{ $_->platform }++ < 1 } @results;
- @results = sort { $a->platform <=> $b->platform } @results;
- }
-
- if ( $backend eq 'iris' and $show_realtime ) {
- if ( $admode eq 'arr' ) {
- @results = sort {
- ( $a->arrival // $a->departure )
- <=> ( $b->arrival // $b->departure )
- } @results;
- }
- else {
- @results = sort {
- ( $a->departure // $a->arrival )
- <=> ( $b->departure // $b->arrival )
- } @results;
- }
- }
-
- for my $result (@results) {
- my $platform = ( split( / /, $result->platform ) )[0];
- my $line = $result->line;
- my $delay = $result->delay;
- if ( $via and $result->can('route') ) {
- my @route = $result->route;
- if ( $result->isa('Travel::Status::DE::IRIS::Result') ) {
- @route = $result->route_post;
- }
- if ( not( List::MoreUtils::any { m{$via}i } @route ) ) {
- next;
- }
- }
- if ( @platforms
- and not( List::MoreUtils::any { $_ eq $platform } @platforms ) )
- {
- next;
- }
- if ( @lines and not( List::MoreUtils::any { $line =~ m{^$_} } @lines ) )
- {
- next;
- }
- if ( $backend eq 'iris' and $admode eq 'arr' and not $result->arrival )
- {
- next;
- }
- if ( $backend eq 'iris'
- and $admode eq 'dep'
- and not $result->departure )
- {
- next;
- }
- my ( $info, $moreinfo );
- if ( $backend eq 'iris' ) {
- my $delaymsg
- = join( ', ', map { $_->[1] } $result->delay_messages );
- my $qosmsg = join( ' +++ ', map { $_->[1] } $result->qos_messages );
- if ( $result->is_cancelled ) {
- $info = "Fahrt fällt aus: ${delaymsg}";
- }
- elsif ( $result->delay and $result->delay > 0 ) {
- if ( $template eq 'clean' ) {
- $info = $delaymsg;
- $delay = $result->delay;
- }
- else {
- $info = sprintf( 'Verspätung ca. %d Min.%s%s',
- $result->delay, $delaymsg ? q{: } : q{}, $delaymsg );
- }
- }
- if ( $result->replacement_for and $template ne 'clean' ) {
- for my $rep ( $result->replacement_for ) {
- $info = sprintf(
- 'Ersatzzug für %s %s %s%s',
- $rep->type, $rep->train_no,
- $info ? '+++ ' : q{}, $info // q{}
- );
- }
- }
- if ( $info and $qosmsg ) {
- $info .= ' +++ ';
- }
- $info .= $qosmsg;
-
- if ( $result->additional_stops and not $result->is_cancelled ) {
- my $additional_line = join( q{, }, $result->additional_stops );
- $info
- = 'Zusätzliche Halte: '
- . $additional_line
- . ( $info ? ' +++ ' : q{} )
- . $info;
- if ( $template ne 'marudor' ) {
- push(
- @{$moreinfo},
- [ 'Zusätzliche Halte', $additional_line ]
- );
- }
- }
-
- if ( $result->canceled_stops and not $result->is_cancelled ) {
- my $cancel_line = join( q{, }, $result->canceled_stops );
- $info
- = 'Ohne Halt in: '
- . $cancel_line
- . ( $info ? ' +++ ' : q{} )
- . $info;
- if ( $template ne 'marudor' ) {
- push( @{$moreinfo}, [ 'Ohne Halt in', $cancel_line ] );
- }
- }
-
- push( @{$moreinfo}, $result->messages );
- }
- else {
- $info = $result->info;
- if ($info) {
- $moreinfo = [ [ 'HAFAS', $info ] ];
- }
- if ( $result->delay and $result->delay > 0 ) {
- if ($info) {
- $info = 'ca. +' . $result->delay . ': ' . $info;
- }
- else {
- $info = 'ca. +' . $result->delay;
- }
- }
- push( @{$moreinfo}, map { [ 'HAFAS', $_ ] } $result->messages );
- }
-
- my $time = $result->time;
-
- if ( $backend eq 'iris' ) {
-
- # ->time defaults to dep, so we only need to overwrite $time
- # if we want arrival times
- if ( $admode eq 'arr' ) {
- $time = $result->sched_arrival->strftime('%H:%M');
- }
-
- if ($show_realtime) {
- if ( ( $admode eq 'arr' and $result->arrival )
- or not $result->departure )
- {
- $time = $result->arrival->strftime('%H:%M');
- }
- else {
- $time = $result->departure->strftime('%H:%M');
- }
- }
- }
-
- if ( $template eq 'clean'
- and $info
- and $info =~ s{ (?: ca [.] \s* )? [+] (\d+) :? \s* }{}x )
- {
- $delay = $1;
- }
- if ( $hide_low_delay and $info ) {
- $info =~ s{ (?: ca [.] \s* )? [+] [ 1 2 3 4 ] $ }{}x;
- }
- if ($info) {
- $info =~ s{ (?: ca [.] \s* )? [+] (\d+) }{Verspätung ca $1 Min.}x;
- }
-
- if ( $template eq 'marudor' ) {
- my @json_route = $self->json_route_diff( [ $result->route ],
- [ $result->sched_route ] );
-
- if ( $apiver == 1 ) {
- push(
- @departures,
- {
- delay => $delay,
- destination => $result->destination,
- isCancelled => $result->can('is_cancelled')
- ? $result->is_cancelled
- : undef,
- messages => {
- delay => [
- map {
- {
- timestamp => $_->[0],
- text => $_->[1]
- }
- } $result->delay_messages
- ],
- qos => [
- map {
- {
- timestamp => $_->[0],
- text => $_->[1]
- }
- } $result->qos_messages
- ],
- },
- platform => $result->platform,
- route => \@json_route,
- scheduledPlatform => $result->sched_platform,
- time => $time,
- train => $result->train,
- via => [ $result->route_interesting(3) ],
- }
- );
- }
- else { # apiver == 2
- my ( $delay_arr, $delay_dep, $sched_arr, $sched_dep );
- if ( $result->arrival ) {
- $delay_arr = $result->arrival->subtract_datetime(
- $result->sched_arrival )->in_units('minutes');
- }
- if ( $result->departure ) {
- $delay_dep = $result->departure->subtract_datetime(
- $result->sched_departure )->in_units('minutes');
- }
- if ( $result->sched_arrival ) {
- $sched_arr = $result->sched_arrival->strftime('%H:%M');
- }
- if ( $result->sched_departure ) {
- $sched_dep = $result->sched_departure->strftime('%H:%M');
- }
- push(
- @departures,
- {
- delayArrival => $delay_arr,
- delayDeparture => $delay_dep,
- destination => $result->destination,
- isCancelled => $result->can('is_cancelled')
- ? $result->is_cancelled
- : undef,
- messages => {
- delay => [
- map {
- {
- timestamp => $_->[0],
- text => $_->[1]
- }
- } $result->delay_messages
- ],
- qos => [
- map {
- {
- timestamp => $_->[0],
- text => $_->[1]
- }
- } $result->qos_messages
- ],
- },
- platform => $result->platform,
- route => \@json_route,
- scheduledPlatform => $result->sched_platform,
- scheduledArrival => $sched_arr,
- scheduledDeparture => $sched_dep,
- train => $result->train,
- via => [ $result->route_interesting(3) ],
- }
- );
- }
- }
- elsif ( $backend eq 'iris' ) {
- push(
- @departures,
- {
- time => $time,
- sched_arrival => $result->sched_arrival
- ? $result->sched_arrival->strftime('%H:%M')
- : undef,
- sched_departure => $result->sched_departure
- ? $result->sched_departure->strftime('%H:%M')
- : undef,
- arrival => $result->arrival
- ? $result->arrival->strftime('%H:%M')
- : undef,
- departure => $result->departure
- ? $result->departure->strftime('%H:%M')
- : undef,
- train => $result->train,
- train_type => $result->type,
- train_line => $result->line_no,
- train_no => $result->train_no,
- via => [ $result->route_interesting(3) ],
- scheduled_route => [ $result->sched_route ],
- route_post => [ $result->route_post ],
- route_post_diff => [
- $self->json_route_diff(
- [ $result->route_post ],
- [ $result->sched_route_post ]
- )
- ],
- destination => $result->destination,
- origin => $result->origin,
- platform => $result->platform,
- scheduled_platform => $result->sched_platform,
- info => $info,
- is_cancelled => $result->is_cancelled,
- messages => {
- delay => [
- map { { timestamp => $_->[0], text => $_->[1] } }
- $result->delay_messages
- ],
- qos => [
- map { { timestamp => $_->[0], text => $_->[1] } }
- $result->qos_messages
- ],
- },
- moreinfo => $moreinfo,
- delay => $delay,
- additional_stops => [ $result->additional_stops ],
- canceled_stops => [ $result->canceled_stops ],
- replaced_by => [
- map { $_->type . q{ } . $_->train_no }
- $result->replaced_by
- ],
- replacement_for => [
- map { $_->type . q{ } . $_->train_no }
- $result->replacement_for
- ],
- }
- );
- }
- else {
- push(
- @departures,
- {
- time => $time,
- train => $result->train,
- train_type => $result->type,
- destination => $result->destination,
- platform => $platform,
- info => $info,
- is_cancelled => $result->can('is_cancelled')
- ? $result->is_cancelled
- : undef,
- messages => {
- delay => [],
- qos => [],
- },
- moreinfo => $moreinfo,
- delay => $delay,
- additional_stops => [],
- canceled_stops => [],
- replaced_by => [],
- replacement_for => [],
- }
- );
- }
- }
-
- if ( $template eq 'json' ) {
- $self->res->headers->access_control_allow_origin(q{*});
- my $json = $self->render_to_string(
- json => {
- api_version => $api_version,
- preformatted => \@departures,
- version => $VERSION,
- raw => \@results,
- }
- );
- if ($callback) {
- $self->render(
- data => "$callback($json);",
- format => 'json'
- );
- }
- else {
- $self->render(
- data => $json,
- format => 'json'
- );
- }
- }
- elsif ( $template eq 'marudor' ) {
- $self->res->headers->access_control_allow_origin(q{*});
- my $json = $self->render_to_string(
- json => {
- departures => \@departures,
- }
- );
- if ($callback) {
- $self->render(
- data => "$callback($json);",
- format => 'json'
- );
- }
- else {
- $self->render(
- data => $json,
- format => 'json'
- );
- }
- }
- else {
- $self->render(
- $template,
- departures => \@departures,
- version => $VERSION,
- title => "departures for ${station}",
- refresh_interval => $refresh_interval + 3,
- hide_opts => $hide_opts,
- show_realtime => $show_realtime,
- );
- }
- return;
-}
-
-get '/_redirect' => sub {
- my $self = shift;
- my $station = $self->param('station');
- my $via = $self->param('via');
- my $params = $self->req->params;
-
- $params->remove('station');
- $params->remove('via');
-
- for my $param (qw(platforms)) {
- if ( not $params->param($param) ) {
- $params->remove($param);
- }
- }
-
- $params = $params->to_string;
-
- if ($via) {
- $self->redirect_to("/${station}/${via}?${params}");
- }
- else {
- $self->redirect_to("/${station}?${params}");
- }
-};
-
-app->defaults( layout => 'default' );
-
-get '/' => \&handle_request;
-get '/:station' => \&handle_request;
-get '/:station/:via' => \&handle_request;
-get '/multi/:station' => \&handle_request;
-
-app->config(
- hypnotoad => {
- accepts => 10,
- listen => ['http://*:8092'],
- pid_file => '/tmp/db-fakedisplay.pid',
- workers => $ENV{VRRFAKEDISPLAY_WORKERS} // 2,
- },
-);
-
-app->types->type( json => 'application/json; charset=utf-8' );
-app->plugin('browser_detect');
-app->start();
+Mojolicious::Commands->start_app('DBInfoscreen');
diff --git a/lib/DBInfoscreen.pm b/lib/DBInfoscreen.pm
new file mode 100644
index 0000000..18a2c87
--- /dev/null
+++ b/lib/DBInfoscreen.pm
@@ -0,0 +1,375 @@
+package DBInfoscreen;
+
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'Mojolicious';
+
+use Cache::File;
+use DBInfoscreen::Helper::DBRIS;
+use DBInfoscreen::Helper::EFA;
+use DBInfoscreen::Helper::HAFAS;
+use DBInfoscreen::Helper::MOTIS;
+use DBInfoscreen::Helper::Wagonorder;
+use File::Slurp qw(read_file);
+use JSON;
+
+use utf8;
+
+sub startup {
+ my ($self) = @_;
+
+ $self->config(
+ hypnotoad => {
+ accepts => $ENV{DBFAKEDISPLAY_ACCEPTS} // 100,
+ clients => $ENV{DBFAKEDISPLAY_CLIENTS} // 10,
+ listen => [ $ENV{DBFAKEDISPLAY_LISTEN} // 'http://*:8092' ],
+ pid_file => $ENV{DBFAKEDISPLAY_PID_FILE}
+ // '/tmp/db-fakedisplay.pid',
+ spare => $ENV{DBFAKEDISPLAY_SPARE} // 2,
+ workers => $ENV{DBFAKEDISPLAY_WORKERS} // 2,
+ },
+ lookahead => $ENV{DBFAKEDISPLAY_LOOKAHEAD} // 180,
+ source_url => 'https://github.com/derf/db-fakedisplay',
+ issue_url => 'https://github.com/derf/db-fakedisplay/issues',
+ version => $ENV{DBFAKEDISPLAY_VERSION} // qx{git describe --dirty}
+ // '???',
+ );
+
+ chomp $self->config->{version};
+ $self->defaults( version => $self->config->{version} // 'UNKNOWN' );
+
+ # Generally, the reverse proxy handles compression.
+ # Also, Mojolicious compression breaks legacy callback-based JSON endpoints
+ # for some clients.
+ $self->renderer->compress(0);
+
+ $self->hook(
+ before_dispatch => sub {
+ my ($self) = @_;
+
+ # The "theme" cookie is set client-side if the theme we delivered was
+ # changed by dark mode detection or by using the theme switcher. It's
+ # not part of Mojolicious' session data (and can't be, due to
+ # signing and HTTPOnly), so we need to add it here.
+
+ for my $cookie ( @{ $self->req->cookies } ) {
+ if ( $cookie->name eq 'theme' ) {
+ $self->session( theme => $cookie->value );
+ }
+ }
+ }
+ );
+
+ $self->attr(
+ cache_iris_main => sub {
+ my ($self) = @_;
+ return Cache::File->new(
+ cache_root => $ENV{DBFAKEDISPLAY_IRIS_CACHE}
+ // '/tmp/dbf-iris-main',
+ default_expires => '6 hours',
+ lock_level => Cache::File::LOCK_LOCAL(),
+ );
+ }
+ );
+
+ $self->attr(
+ cache_iris_rt => sub {
+ my ($self) = @_;
+ return Cache::File->new(
+ cache_root => $ENV{DBFAKEDISPLAY_IRISRT_CACHE}
+ // '/tmp/dbf-iris-realtime',
+ default_expires => '70 seconds',
+ lock_level => Cache::File::LOCK_LOCAL(),
+ );
+ }
+ );
+
+ $self->attr(
+ dbdb_wagon => sub {
+ return JSON->new->utf8->decode(
+ scalar read_file('share/dbdb_wagen.json') );
+ }
+ );
+
+ $self->helper(
+ dbris => sub {
+ my ($self) = @_;
+ state $efa = DBInfoscreen::Helper::DBRIS->new(
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
+ );
+ }
+ );
+
+ $self->helper(
+ motis => sub {
+ my ($self) = @_;
+ state $motis = DBInfoscreen::Helper::MOTIS->new(
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
+ );
+ }
+ );
+
+ $self->helper(
+ efa => sub {
+ my ($self) = @_;
+ state $efa = DBInfoscreen::Helper::EFA->new(
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
+ );
+ }
+ );
+
+ $self->helper(
+ hafas => sub {
+ my ($self) = @_;
+ state $hafas = DBInfoscreen::Helper::HAFAS->new(
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
+ );
+ }
+ );
+
+ $self->helper(
+ wagonorder => sub {
+ my ($self) = @_;
+ state $hafas = DBInfoscreen::Helper::Wagonorder->new(
+ log => $self->app->log,
+ main_cache => $self->app->cache_iris_main,
+ realtime_cache => $self->app->cache_iris_rt,
+ root_url => $self->url_for('/')->to_abs,
+ user_agent => $self->ua,
+ version => $self->config->{version},
+ );
+ }
+ );
+
+ $self->helper(
+ wagon_image => sub {
+ my ( $self, $train_type, $wagon_type, $uic ) = @_;
+ my $ret;
+ if ( $train_type =~ m{IC(?!E)}
+ and $wagon_type
+ =~ m{ ^ [AB] R? k? [ipv] m m? b? d? s? z f? $ }x )
+ {
+ $ret = $wagon_type;
+ }
+ elsif ( $train_type =~ m{IC2.TWIN} ) {
+ $ret = $wagon_type;
+ }
+ elsif ( not $uic ) {
+ return;
+ }
+ else {
+ $ret = substr( $uic, 4, 5 );
+ }
+
+ if ( $train_type =~ m{[.]S(\d)$} ) {
+ $ret .= ".$1";
+ }
+ elsif ( $train_type =~ m{[.]R$} ) {
+ $ret .= '.r';
+ }
+
+ if ( $ret and $self->app->dbdb_wagon->{$ret} ) {
+ return $ret;
+ }
+ return;
+ }
+ );
+
+ $self->helper(
+ 'is_important' => sub {
+ my ( $self, $stop ) = @_;
+
+ # Centraal: dutch main station (Hbf in .nl)
+ # HB: swiss main station (Hbf in .ch)
+ # hl.n.: czech main station (Hbf in .cz)
+ if ( $stop =~ m{ HB $ | hl\.n\. $ | Hbf | Centraal | Flughafen }x )
+ {
+ return 1;
+ }
+ if ( ( $self->param('hafas') or $self->param('efa') )
+ and $stop =~ m{ [Bb]ahnhof | Bf }x )
+ {
+ return 1;
+ }
+ return;
+ }
+ );
+
+ $self->helper(
+ 'occupancy_icon' => sub {
+ my ( $self, $occupancy ) = @_;
+
+ my @symbols
+ = (
+ qw(help_outline person_outline people priority_high not_interested)
+ );
+ my $text = 'Auslastung unbekannt';
+
+ if ( $occupancy eq 'MANY_SEATS' ) {
+ $occupancy = 1;
+ }
+ elsif ( $occupancy eq 'FEW_SEATS' ) {
+ $occupancy = 2;
+ }
+ elsif ( $occupancy eq 'STANDING_ONLY' ) {
+ $occupancy = 3;
+ }
+ elsif ( $occupancy eq 'FULL' ) {
+ $occupancy = 4;
+ }
+
+ if ( $occupancy > 3 ) {
+ $text = 'Voraussichtlich überfüllt';
+ }
+ if ( $occupancy > 2 ) {
+ $text = 'Sehr hohe Auslastung erwartet';
+ }
+ elsif ( $occupancy > 1 ) {
+ $text = 'Hohe Auslastung erwartet';
+ }
+ elsif ( $occupancy > 0 ) {
+ $text = 'Geringe Auslastung erwartet';
+ }
+
+ return ( $text, $symbols[$occupancy] );
+ }
+ );
+
+ $self->helper(
+ 'utilization_icon' => sub {
+ my ( $self, $utilization ) = @_;
+ my ( $first, $second ) = @{ $utilization // [] };
+ $first //= 0;
+ $second //= 0;
+ my $sum = ( $first + $second ) / 2;
+
+ my @symbols
+ = (
+ qw(help_outline person_outline people priority_high not_interested)
+ );
+ my $text = 'Auslastung unbekannt';
+
+ if ( $sum > 3.5 ) {
+ $text = 'Zug ist ausgebucht';
+ }
+ elsif ( $sum >= 2.5 ) {
+ $text = 'Sehr hohe Auslastung';
+ }
+ elsif ( $sum >= 1.5 ) {
+ $text = 'Hohe Auslastung';
+ }
+ elsif ( $sum >= 1 ) {
+ $text = 'Geringe Auslastung';
+ }
+
+ return ( $text, $symbols[$first], $symbols[$second] );
+ }
+ );
+
+ $self->helper(
+ 'numeric_platform_part' => sub {
+ my ( $self, $platform ) = @_;
+
+ if ( not defined $platform ) {
+ return 0;
+ }
+
+ if ( $platform =~ m{ ^ \d+ $ }x ) {
+ return $platform;
+ }
+
+ if ( $platform =~ m{ (\d+) }x ) {
+ return $1;
+ }
+
+ return 0;
+ }
+ );
+
+ $self->helper(
+ 'get_rt_time_class' => sub {
+ my ( $self, $train ) = @_;
+ if ( $train->{has_realtime}
+ and not $train->{is_bit_delayed}
+ and not $train->{is_delayed} )
+ {
+ return 'on-time';
+ }
+ if ( $train->{is_bit_delayed} ) {
+ return 'a-bit-delayed';
+ }
+ if ( $train->{is_delayed} ) {
+ return 'delayed';
+ }
+ return q{};
+ }
+ );
+
+ my $r = $self->routes;
+
+ $r->get('/_redirect')->to('stationboard#redirect_to_station');
+
+ # legacy entry point
+ $r->get('/_auto')->to('static#geostop');
+
+ $r->get('/_autostop')->to('static#geostop');
+
+ $r->get('/_backend')->to('stationboard#backend_list');
+
+ $r->get('/_datenschutz')->to('static#privacy');
+
+ $r->post('/_geolocation')->to('stationboard#stations_by_coordinates');
+
+ $r->get('/_about')->to('static#about');
+
+ $r->get('/_impressum')->to('static#imprint');
+
+ $r->get('/dyn/:av/autocomplete.js')->to('stationboard#autocomplete');
+
+ $r->get('/carriage-formation')->to('wagenreihung#wagenreihung');
+ $r->get('/w/*wagon')->to('wagenreihung#wagen');
+
+ $r->get('/_ajax_mapinfo/:tripid/:lineno')->to('map#ajax_route');
+ $r->get('/map/:tripid/:lineno')->to('map#route');
+ $r->get('/coverage/:backend/:service')->to('map#coverage');
+ $r->get( '/z/:train/*station' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#station_train_details', format => undef )
+ ->name('train_at_station');
+ $r->get( '/z/:train' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#train_details', format => undef )
+ ->name('train');
+
+ $self->defaults( layout => 'app' );
+
+ $r->get('/')->to('stationboard#handle_board_request');
+ $r->get('/multi/*station')->to('stationboard#handle_board_request');
+ $r->get( '/*station' => [ format => [ 'html', 'json' ] ] )
+ ->to( 'stationboard#handle_board_request', format => undef );
+
+ $self->types->type( json => 'application/json; charset=utf-8' );
+
+}
+
+1;
diff --git a/lib/DBInfoscreen/Controller/Map.pm b/lib/DBInfoscreen/Controller/Map.pm
new file mode 100644
index 0000000..0a597e1
--- /dev/null
+++ b/lib/DBInfoscreen/Controller/Map.pm
@@ -0,0 +1,1389 @@
+package DBInfoscreen::Controller::Map;
+
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+# Copyright (C) 2025 networkException <git@nwex.de>
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'Mojolicious::Controller';
+use Mojo::JSON qw(decode_json encode_json);
+use Mojo::Promise;
+
+use DateTime;
+use DateTime::Format::Strptime;
+use GIS::Distance;
+use List::Util qw();
+
+my $strp = DateTime::Format::Strptime->new(
+ pattern => '%Y-%m-%dT%H:%M:%S%z',
+ time_zone => 'Europe/Berlin',
+);
+
+# Input:
+# - polyline: [{lat, lon, name?}, ...]
+# - from_name: station name
+# - to_name: station name
+# Ouptut:
+# - from_index: polyline index where name eq from_name
+# - to_index: polyline index where name eq to_name
+sub get_route_indexes {
+ my ( $polyline, $from_name, $to_name ) = @_;
+ my ( $from_index, $to_index );
+
+ for my $i ( 0 .. $#{$polyline} ) {
+ my $this_point = $polyline->[$i];
+ my $name = $this_point->{name} // $this_point->{stop}->{name};
+
+ if ( not defined $from_index
+ and $name
+ and $name eq $from_name )
+ {
+ $from_index = $i;
+ }
+ elsif ( $name
+ and $name eq $to_name )
+ {
+ $to_index = $i;
+ last;
+ }
+ }
+ return ( $from_index, $to_index );
+}
+
+# Input:
+# now: DateTime
+# from: current/previous stop
+# {arr => DateTime, dep => DateTime, name => str, lat => float, lon => float}
+# to: next stop
+# {arr => DateTime, dep => DateTime, name => str, lat => float, lon => float}
+# route: Travel::Status::DE::HAFAS::Journey->route
+# polyline: Travel::Status::DE::HAFAS::Journey->polyline (list of lon/lat hashes)
+# Output: list of estimated train positions in [lat, lon] format.
+# - current position
+# - position 2 seconds from now
+# - position 4 seconds from now
+# - ...
+sub estimate_train_positions {
+ my ( $self, %opt ) = @_;
+
+ my $now = $opt{now};
+
+ my $from_dt = $opt{from}{dep} // $opt{from}{arr};
+ my $to_dt = $opt{to}{arr} // $opt{to}{dep};
+ my $from_name = $opt{from}{name};
+ my $to_name = $opt{to}{name};
+ my $route = $opt{route};
+ my $polyline = $opt{polyline};
+
+ my @train_positions;
+
+ my $time_complete = $now->epoch - $from_dt->epoch;
+ my $time_total = $to_dt->epoch - $from_dt->epoch;
+
+ my @completion_ratios
+ = map { ( $time_complete + ( $_ * 2 ) ) / $time_total } ( 0 .. 45 );
+
+ my $distance = GIS::Distance->new;
+
+ my ( $from_index, $to_index )
+ = get_route_indexes( $polyline, $from_name, $to_name );
+
+ if ( defined $from_index and defined $to_index ) {
+ my $total_distance = 0;
+ for my $j ( $from_index + 1 .. $to_index ) {
+ my $prev = $polyline->[ $j - 1 ];
+ my $this = $polyline->[$j];
+ if ( $prev and $this ) {
+ $total_distance
+ += $distance->distance_metal( $prev->{lat}, $prev->{lon},
+ $this->{lat}, $this->{lon} );
+ }
+ }
+ my @marker_distances = map { $total_distance * $_ } @completion_ratios;
+ $total_distance = 0;
+ for my $j ( $from_index + 1 .. $to_index ) {
+ my $prev = $polyline->[ $j - 1 ];
+ my $this = $polyline->[$j];
+ if ( $prev and $this ) {
+ my $prev_distance = $total_distance;
+ $total_distance
+ += $distance->distance_metal( $prev->{lat}, $prev->{lon},
+ $this->{lat}, $this->{lon} );
+ for my $i ( @train_positions .. $#marker_distances ) {
+ my $marker_distance = $marker_distances[$i];
+ if ( $total_distance > $marker_distance ) {
+
+ # completion ratio for the line between (prev, this)
+ my $sub_ratio = 1;
+ if ( $total_distance != $prev_distance ) {
+ $sub_ratio = ( $marker_distance - $prev_distance )
+ / ( $total_distance - $prev_distance );
+ }
+
+ my $lat = $prev->{lat}
+ + ( $this->{lat} - $prev->{lat} ) * $sub_ratio;
+ my $lon = $prev->{lon}
+ + ( $this->{lon} - $prev->{lon} ) * $sub_ratio;
+
+ push( @train_positions, [ $lat, $lon ] );
+ }
+ }
+ if ( @train_positions == @completion_ratios ) {
+ return @train_positions;
+ }
+ }
+ }
+ if (@train_positions) {
+ return @train_positions;
+ }
+ }
+ else {
+ $self->log->debug(
+ "Did not find route indexes for $from_name → $to_name");
+ $self->log->debug(
+"Falling back to $opt{from}{lat} $opt{from}{lon} → $opt{to}{lat} $opt{to}{lon}"
+ );
+ for my $ratio (@completion_ratios) {
+ my $lat
+ = $opt{from}{lat} + ( $opt{to}{lat} - $opt{from}{lat} ) * $ratio;
+ my $lon
+ = $opt{from}{lon} + ( $opt{to}{lon} - $opt{from}{lon} ) * $ratio;
+ push( @train_positions, [ $lat, $lon ] );
+ }
+ return @train_positions;
+ }
+ return [ $opt{to}{lat}, $opt{to}{lon} ];
+}
+
+# Input:
+# now: DateTime
+# route: arrayref of hashrefs
+# lat: float
+# lon: float
+# name: str
+# arr: DateTime
+# dep: DateTime
+# arr_delay: int
+# dep_delay: int
+# polyline: ref to Travel::Status::DE::HAFAS::Journey polyline list
+# Output:
+# next_stop: {type, station}
+# positions: [current position [lat, lon], 2s from now, 4s from now, ...]
+sub estimate_train_positions2 {
+ my ( $self, %opt ) = @_;
+ my $now = $opt{now};
+ my @route = @{ $opt{route} // [] };
+
+ my @train_positions;
+ my $next_stop;
+ my $distance = GIS::Distance->new;
+ my $stop_distance_sum = 0;
+ my $avg_inter_stop_beeline = 0;
+
+ for my $i ( 1 .. $#route ) {
+ if ( not $next_stop
+ and ( $route[$i]{arr} // $route[$i]{dep} )
+ and ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} )
+ and $now > ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} )
+ and $now < ( $route[$i]{arr} // $route[$i]{dep} ) )
+ {
+
+ # HAFAS does not provide delays for past stops
+ $self->backpropagate_delay( $route[ $i - 1 ], $route[$i] );
+
+ # (current position, future positons...) in 2 second steps
+ @train_positions = $self->estimate_train_positions(
+ from => $route[ $i - 1 ],
+ to => $route[$i],
+ now => $now,
+ route => $opt{route},
+ polyline => $opt{polyline},
+ );
+
+ $next_stop = {
+ type => 'next',
+ station => $route[$i],
+ };
+ }
+ if ( not $next_stop
+ and ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} )
+ and $now <= ( $route[ $i - 1 ]{dep} // $route[ $i - 1 ]{arr} ) )
+ {
+ @train_positions
+ = ( [ $route[ $i - 1 ]{lat}, $route[ $i - 1 ]{lon} ] );
+ $next_stop = {
+ type => 'present',
+ station => $route[ $i - 1 ],
+ };
+ }
+ $stop_distance_sum += $distance->distance_metal(
+ $route[ $i - 1 ]{lat}, $route[ $i - 1 ]{lon},
+ $route[$i]{lat}, $route[$i]{lon}
+ ) / 1000;
+ }
+
+ if ($#route) {
+ $avg_inter_stop_beeline = $stop_distance_sum / $#route;
+ }
+
+ if ( @route and not $next_stop ) {
+ @train_positions = ( [ $route[-1]{lat}, $route[-1]{lon} ] );
+ $next_stop = {
+ type => 'present',
+ station => $route[-1]
+ };
+ }
+
+ my $position_now = shift @train_positions;
+
+ return {
+ next_stop => $next_stop,
+ avg_inter_stop_beeline => $avg_inter_stop_beeline,
+ position_now => $position_now,
+ positions => \@train_positions,
+ };
+}
+
+# input: [{
+# name, platform,
+# arr, arr_cancelled, arr_delay,
+# dep, dep_cancelled, dep_delay
+# }]
+sub route_to_ajax {
+ my (@stopovers) = @_;
+
+ my @route_entries;
+
+ for my $stop (@stopovers) {
+ my @stop_entries = ( $stop->{name} );
+ my $platform;
+
+ if ( my $arr = $stop->{arr} and not $stop->{arr_cancelled} ) {
+ my $delay = $stop->{arr_delay} // 0;
+ $platform = $stop->{platform};
+
+ push( @stop_entries, $arr->epoch, $delay );
+ }
+ else {
+ push( @stop_entries, q{}, q{} );
+ }
+
+ if ( my $dep = $stop->{dep} and not $stop->{dep_cancelled} ) {
+ my $delay = $stop->{dep_delay} // 0;
+ $platform //= $stop->{platform} // q{};
+
+ push( @stop_entries, $dep->epoch, $delay, $platform );
+ }
+ else {
+ push( @stop_entries, q{}, q{}, q{} );
+ }
+
+ push( @route_entries, join( ';', @stop_entries ) );
+ }
+
+ return join( '|', @route_entries );
+}
+
+sub polyline_to_line_pairs {
+ my (@polyline) = @_;
+ my @line_pairs;
+ for my $i ( 1 .. $#polyline ) {
+ push(
+ @line_pairs,
+ [
+ [ $polyline[ $i - 1 ]{lat}, $polyline[ $i - 1 ]{lon} ],
+ [ $polyline[$i]{lat}, $polyline[$i]{lon} ]
+ ]
+ );
+ }
+ return @line_pairs;
+}
+
+sub backpropagate_delay {
+ my ( $self, $prev_stop, $next_stop ) = @_;
+
+ if ( ( $next_stop->{arr_delay} || $next_stop->{dep_delay} )
+ and not( $prev_stop->{dep_delay} || $prev_stop->{arr_delay} ) )
+ {
+ $self->log->debug("need to back-propagate delay");
+ my $delay = $next_stop->{arr_delay} || $next_stop->{dep_delay};
+ if ( $prev_stop->{arr} ) {
+ $prev_stop->{arr}->add( minutes => $delay );
+ $prev_stop->{arr_delay} = $delay;
+ }
+ if ( $prev_stop->{dep} ) {
+ $prev_stop->{dep}->add( minutes => $delay );
+ $prev_stop->{dep_delay} = $delay;
+ }
+ }
+}
+
+sub route_efa {
+ my ($self) = @_;
+ my $trip_id = $self->stash('tripid');
+ my $backend = $self->param('efa');
+
+ my $stopseq;
+ if ( $trip_id
+ =~ m{ ^ ([^@]*) @ ([^@]*) [(] ([^T]*) T ([^)]*) [)] (.*) $ }x )
+ {
+ $stopseq = {
+ stateless => $1,
+ stop_id => $2,
+ date => $3,
+ time => $4,
+ key => $5
+ };
+ }
+ else {
+ $self->render(
+ 'route_map',
+ title => "DBF",
+ hide_opts => 1,
+ with_map => 1,
+ error => "cannot parse trip ID: $trip_id",
+ );
+ return;
+ }
+
+ $self->efa->get_polyline_p(
+ stopseq => $stopseq,
+ service => $backend,
+ )->then(
+ sub {
+ my ($trip) = @_;
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+ my @markers;
+ my @polyline = $trip->polyline( fallback => 1 );
+ my @line_pairs = polyline_to_line_pairs(@polyline);
+ my @route = $trip->route;
+
+ my $ref_route = [
+ map {
+ {
+ name => $_->full_name,
+ platform => $_->platform,
+ arr => $_->arr,
+ dep => $_->dep,
+ arr_delay => $_->arr_delay,
+ dep_delay => $_->dep_delay,
+ lat => $_->latlon->[0],
+ lon => $_->latlon->[1]
+ }
+ } @route
+ ];
+
+ for my $pl (@polyline) {
+ if ( $pl->{stop} ) {
+ $pl->{name} = $pl->{stop}->full_name;
+ }
+ }
+
+ my $train_pos = $self->estimate_train_positions2(
+ now => $now,
+ route => $ref_route,
+ polyline => \@polyline,
+ );
+
+ my @station_coordinates;
+ for my $stop (@route) {
+ my @stop_lines = ( $stop->full_name );
+ if ( $stop->platform ) {
+ push( @stop_lines, 'Gleis ' . $stop->platform );
+ }
+ if ( $stop->arr ) {
+ my $arr_line = $stop->arr->strftime('Ankunft: %H:%M');
+ if ( $stop->arr_delay ) {
+ $arr_line .= sprintf( ' (%+d)', $stop->arr_delay );
+ }
+ push( @stop_lines, $arr_line );
+ }
+ if ( $stop->dep ) {
+ my $dep_line = $stop->dep->strftime('Abfahrt: %H:%M');
+ if ( $stop->dep_delay ) {
+ $dep_line .= sprintf( ' (%+d)', $stop->dep_delay );
+ }
+ push( @stop_lines, $dep_line );
+ }
+
+ push( @station_coordinates, [ $stop->latlon, [@stop_lines], ] );
+ }
+
+ push(
+ @markers,
+ {
+ lat => $train_pos->{position_now}[0],
+ lon => $train_pos->{position_now}[1],
+ title => $trip->name,
+ }
+ );
+
+ $self->render(
+ 'route_map',
+ description => "Karte für " . $trip->name,
+ title => $trip->name,
+ hide_opts => 1,
+ with_map => 1,
+ ajax_req => "${trip_id}/0",
+ ajax_route => route_to_ajax( @{$ref_route} ),
+ ajax_polyline => join( '|',
+ map { join( ';', @{$_} ) } @{ $train_pos->{positions} } ),
+ origin => {
+ name => ( $trip->route )[0]->full_name,
+ ts => ( $trip->route )[0]->dep,
+ },
+ destination => {
+ name => ( $trip->route )[-1]->full_name,
+ ts => ( $trip->route )[-1]->arr,
+ },
+ train_no => $trip->number
+ ? ( $trip->type // q{} . ' ' . $trip->number )
+ : undef,
+ operator => $trip->operator,
+ next_stop => $train_pos->{next_stop},
+ polyline_groups => [
+ {
+ polylines => \@line_pairs,
+ color => '#00838f',
+ opacity => 0.6,
+ fit_bounds => 1,
+ }
+ ],
+ station_coordinates => \@station_coordinates,
+ station_radius => 100,
+ markers => \@markers,
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ 'route_map',
+ title => "DBF",
+ hide_opts => 1,
+ with_map => 1,
+ error => $err,
+ );
+ }
+ )->wait;
+}
+
+sub route_dbris {
+ my ($self) = @_;
+ my $trip_id = $self->stash('tripid');
+
+ my $from_name = $self->param('from');
+ my $to_name = $self->param('to');
+
+ $self->dbris->get_polyline_p( id => $trip_id )->then(
+ sub {
+ my ($journey) = @_;
+
+ my @polyline = $journey->polyline;
+ my @station_coordinates;
+
+ my @markers;
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ # used to draw the train's journey on the map
+ my @line_pairs = polyline_to_line_pairs(@polyline);
+
+ my @route = $journey->route;
+
+ my $train_pos = $self->estimate_train_positions2(
+ now => $now,
+ route => [
+ map {
+ {
+ name => $_->name,
+ arr => $_->arr,
+ dep => $_->dep,
+ arr_delay => $_->arr_delay,
+ dep_delay => $_->dep_delay,
+ lat => $_->lat,
+ lon => $_->lon
+ }
+ } @route
+ ],
+ polyline => \@polyline,
+ );
+
+ # Prepare from/to markers and name/time/delay overlays for stations
+ for my $stop (@route) {
+ my @stop_lines = ( $stop->name );
+
+ if ( $from_name and $stop->name eq $from_name ) {
+ push(
+ @markers,
+ {
+ lon => $stop->lon,
+ lat => $stop->lat,
+ title => $stop->name,
+ icon => 'goldIcon',
+ }
+ );
+ }
+ if ( $to_name and $stop->name eq $to_name ) {
+ push(
+ @markers,
+ {
+ lon => $stop->lon,
+ lat => $stop->lat,
+ title => $stop->name,
+ icon => 'greenIcon',
+ }
+ );
+ }
+
+ if ( $stop->platform ) {
+ push( @stop_lines, 'Gleis ' . $stop->platform );
+ }
+ if ( $stop->arr ) {
+ my $arr_line = $stop->arr->strftime('Ankunft: %H:%M');
+ if ( $stop->arr_delay ) {
+ $arr_line .= sprintf( ' (%+d)', $stop->arr_delay );
+ }
+ push( @stop_lines, $arr_line );
+ }
+ if ( $stop->dep ) {
+ my $dep_line = $stop->dep->strftime('Abfahrt: %H:%M');
+ if ( $stop->dep_delay ) {
+ $dep_line .= sprintf( ' (%+d)', $stop->dep_delay );
+ }
+ push( @stop_lines, $dep_line );
+ }
+
+ push( @station_coordinates,
+ [ [ $stop->lat, $stop->lon ], [@stop_lines], ] );
+ }
+
+ push(
+ @markers,
+ {
+ lat => $train_pos->{position_now}[0],
+ lon => $train_pos->{position_now}[1],
+ title => $journey->train,
+ }
+ );
+
+ $self->render(
+ 'route_map',
+ description => "Karte für " . $journey->train,
+ title => $journey->train,
+ hide_opts => 1,
+ with_map => 1,
+ ajax_req => "${trip_id}/0",
+ ajax_route => route_to_ajax(
+ map {
+ {
+ name => $_->name,
+ platform => $_->platform,
+ arr => $_->arr,
+ arr_cancelled => $_->is_cancelled,
+ arr_delay => $_->arr_delay,
+ dep => $_->dep,
+ dep_cancelled => $_->is_cancelled,
+ dep_delay => $_->dep_delay,
+ }
+ } $journey->route
+ ),
+ ajax_polyline => join(
+ '|',
+ map { join( ';', @{$_} ) } @{ $train_pos->{positions} }
+ ),
+ origin => {
+ name => ( $journey->route )[0]->name,
+ ts => ( $journey->route )[0]->dep,
+ },
+ destination => {
+ name => ( $journey->route )[-1]->name,
+ ts => ( $journey->route )[-1]->arr,
+ },
+ train_no => $journey->number
+ ? ( $journey->type // q{} . ' ' . $journey->number )
+ : undef,
+ next_stop => $train_pos->{next_stop},
+ polyline_groups => [
+ {
+ polylines => [@line_pairs],
+ color => '#00838f',
+ opacity => 0.6,
+ fit_bounds => 1,
+ }
+ ],
+ station_coordinates => [@station_coordinates],
+ station_radius =>
+ ( $train_pos->{avg_inter_stop_beeline} > 500 ? 250 : 100 ),
+ markers => [@markers],
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ 'route_map',
+ title => "DBF",
+ hide_opts => 1,
+ with_map => 1,
+ error => $err,
+ );
+
+ }
+ )->wait;
+}
+
+sub route_motis {
+ my ($self) = @_;
+
+ my $service = $self->param('motis') // 'transitous';
+ my $trip_id = $self->stash('tripid');
+
+ my $from_name = $self->param('from');
+ my $to_name = $self->param('to');
+
+ $self->motis->get_polyline_p(
+ service => $service,
+ id => $trip_id,
+ )->then(
+ sub {
+ my ($trip) = @_;
+
+ my @polyline = $trip->polyline;
+ my @station_coordinates;
+
+ my @markers;
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ # used to draw the train's journey on the map
+ my @line_pairs = polyline_to_line_pairs(@polyline);
+
+ my @stopovers = $trip->stopovers;
+
+ my $train_pos = $self->estimate_train_positions2(
+ now => $now,
+ route => [
+ map {
+ {
+ name => $_->stop->name,
+ arr => $_->arrival,
+ dep => $_->departure,
+ arr_delay => $_->arrival_delay,
+ dep_delay => $_->departure_delay,
+ lat => $_->stop->lat,
+ lon => $_->stop->lon,
+ }
+ } @stopovers
+ ],
+ polyline => \@polyline,
+ );
+
+ # Prepare from/to markers and name/time/delay overlays for stations
+ for my $stopover (@stopovers) {
+ my $stop = $stopover->stop;
+ my @stop_lines = ( $stop->name );
+
+ if ( $from_name and $stop->name eq $from_name ) {
+ push(
+ @markers,
+ {
+ lon => $stop->lon,
+ lat => $stop->lat,
+ title => $stop->name,
+ icon => 'goldIcon',
+ }
+ );
+ }
+ if ( $to_name and $stop->name eq $to_name ) {
+ push(
+ @markers,
+ {
+ lon => $stop->lon,
+ lat => $stop->lat,
+ title => $stop->name,
+ icon => 'greenIcon',
+ }
+ );
+ }
+
+ if ( $stopover->track ) {
+ push( @stop_lines, 'Gleis ' . $stop->track );
+ }
+ if ( $stopover->arrival ) {
+ my $arr_line
+ = $stopover->arrival->strftime('Ankunft: %H:%M');
+ if ( $stopover->arrival_delay ) {
+ $arr_line
+ .= sprintf( ' (%+d)', $stopover->arrival_delay );
+ }
+ push( @stop_lines, $arr_line );
+ }
+ if ( $stopover->departure ) {
+ my $dep_line
+ = $stopover->departure->strftime('Abfahrt: %H:%M');
+ if ( $stopover->departure_delay ) {
+ $dep_line
+ .= sprintf( ' (%+d)', $stopover->departure_delay );
+ }
+ push( @stop_lines, $dep_line );
+ }
+
+ push( @station_coordinates,
+ [ [ $stop->lat, $stop->lon ], [@stop_lines], ] );
+ }
+
+ push(
+ @markers,
+ {
+ lat => $train_pos->{position_now}[0],
+ lon => $train_pos->{position_now}[1],
+ title => $trip->route_name,
+ }
+ );
+
+ $self->render(
+ 'route_map',
+ description => "Karte für " . $trip->route_name,
+ title => $trip->route_name,
+ hide_opts => 1,
+ with_map => 1,
+ ajax_req => "${trip_id}/0",
+ ajax_route => route_to_ajax(
+ map {
+ {
+ name => $_->stop->name,
+ platform => $_->track,
+ arr => $_->arrival,
+ arr_cancelled => $_->is_cancelled,
+ arr_delay => $_->arrival_delay,
+ dep => $_->departure,
+ dep_cancelled => $_->is_cancelled,
+ dep_delay => $_->departure_delay,
+ }
+ } $trip->stopovers
+ ),
+ ajax_polyline => join(
+ '|',
+ map { join( ';', @{$_} ) } @{ $train_pos->{positions} }
+ ),
+ origin => {
+ name => ( $trip->stopovers )[0]->stop->name,
+ ts => ( $trip->stopovers )[0]->departure,
+ },
+ destination => {
+ name => ( $trip->stopovers )[-1]->stop->name,
+ ts => ( $trip->stopovers )[-1]->arrival,
+ },
+ train_no => undef, # FIXME: Better value?
+ next_stop => $train_pos->{next_stop},
+ polyline_groups => [
+ {
+ polylines => [@line_pairs],
+ color => '#00838f',
+ opacity => 0.6,
+ fit_bounds => 1,
+ }
+ ],
+ station_coordinates => [@station_coordinates],
+ station_radius =>
+ ( $train_pos->{avg_inter_stop_beeline} > 500 ? 250 : 100 ),
+ markers => [@markers],
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ 'route_map',
+ title => "DBF",
+ hide_opts => 1,
+ with_map => 1,
+ error => $err,
+ );
+
+ }
+ )->wait;
+}
+
+sub route {
+ my ($self) = @_;
+ my $trip_id = $self->stash('tripid');
+ my $line_no = $self->stash('lineno');
+ my $hafas = $self->param('hafas');
+
+ my $from_name = $self->param('from');
+ my $to_name = $self->param('to');
+
+ $self->render_later;
+
+ if ( $self->param('dbris') ) {
+ return $self->route_dbris;
+ }
+ if ( $self->param('motis') ) {
+ return $self->route_motis;
+ }
+ if ( $self->param('efa') ) {
+ return $self->route_efa;
+ }
+
+ my $service = 'ÖBB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
+ $self->hafas->get_polyline_p(
+ id => $trip_id,
+ line => $line_no,
+ service => $service
+ )->then(
+ sub {
+ my ($journey) = @_;
+
+ my @polyline = $journey->polyline;
+ my @station_coordinates;
+
+ my @markers;
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ # used to draw the train's journey on the map
+ my @line_pairs = polyline_to_line_pairs(@polyline);
+
+ my @route = $journey->route;
+
+ my $train_pos = $self->estimate_train_positions2(
+ now => $now,
+ route => [
+ map {
+ {
+ name => $_->loc->name,
+ arr => $_->arr,
+ dep => $_->dep,
+ arr_delay => $_->arr_delay,
+ dep_delay => $_->dep_delay,
+ lat => $_->loc->lat,
+ lon => $_->loc->lon
+ }
+ } @route
+ ],
+ polyline => \@polyline,
+ );
+
+ # Prepare from/to markers and name/time/delay overlays for stations
+ for my $stop (@route) {
+ my @stop_lines = ( $stop->loc->name );
+
+ if ( $from_name and $stop->loc->name eq $from_name ) {
+ push(
+ @markers,
+ {
+ lon => $stop->loc->lon,
+ lat => $stop->loc->lat,
+ title => $stop->loc->name,
+ icon => 'goldIcon',
+ }
+ );
+ }
+ if ( $to_name and $stop->loc->name eq $to_name ) {
+ push(
+ @markers,
+ {
+ lon => $stop->loc->lon,
+ lat => $stop->loc->lat,
+ title => $stop->loc->name,
+ icon => 'greenIcon',
+ }
+ );
+ }
+
+ if ( $stop->platform ) {
+ push( @stop_lines, 'Gleis ' . $stop->platform );
+ }
+ if ( $stop->arr ) {
+ my $arr_line = $stop->arr->strftime('Ankunft: %H:%M');
+ if ( $stop->arr_delay ) {
+ $arr_line .= sprintf( ' (%+d)', $stop->arr_delay );
+ }
+ push( @stop_lines, $arr_line );
+ }
+ if ( $stop->dep ) {
+ my $dep_line = $stop->dep->strftime('Abfahrt: %H:%M');
+ if ( $stop->dep_delay ) {
+ $dep_line .= sprintf( ' (%+d)', $stop->dep_delay );
+ }
+ push( @stop_lines, $dep_line );
+ }
+
+ push( @station_coordinates,
+ [ [ $stop->loc->lat, $stop->loc->lon ], [@stop_lines], ] );
+ }
+
+ push(
+ @markers,
+ {
+ lat => $train_pos->{position_now}[0],
+ lon => $train_pos->{position_now}[1],
+ title => $journey->name
+ }
+ );
+
+ $self->render(
+ 'route_map',
+ description => "Karte für " . $journey->name,
+ title => $journey->name,
+ hide_opts => 1,
+ with_map => 1,
+ ajax_req => "${trip_id}/${line_no}",
+ ajax_route => route_to_ajax(
+ map {
+ {
+ name => $_->loc->name,
+ platform => $_->platform,
+ arr => $_->arr,
+ arr_cancelled => $_->arr_cancelled,
+ arr_delay => $_->arr_delay,
+ dep => $_->dep,
+ dep_cancelled => $_->dep_cancelled,
+ dep_delay => $_->dep_delay,
+ }
+ } $journey->route
+ ),
+ ajax_polyline => join(
+ '|',
+ map { join( ';', @{$_} ) } @{ $train_pos->{positions} }
+ ),
+ origin => {
+ name => ( $journey->route )[0]->loc->name,
+ ts => ( $journey->route )[0]->dep,
+ },
+ destination => {
+ name => $journey->route_end,
+ ts => ( $journey->route )[-1]->arr,
+ },
+ train_no => $journey->number
+ ? ( $journey->type // q{} . ' ' . $journey->number )
+ : undef,
+ operator => $journey->operator,
+ next_stop => $train_pos->{next_stop},
+ polyline_groups => [
+ {
+ polylines => [@line_pairs],
+ color => '#00838f',
+ opacity => 0.6,
+ fit_bounds => 1,
+ }
+ ],
+ station_coordinates => [@station_coordinates],
+ station_radius =>
+ ( $train_pos->{avg_inter_stop_beeline} > 500 ? 250 : 100 ),
+ markers => [@markers],
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ 'route_map',
+ title => "DBF",
+ hide_opts => 1,
+ with_map => 1,
+ error => $err,
+ );
+
+ }
+ )->wait;
+}
+
+sub ajax_route_efa {
+ my ($self) = @_;
+ my $backend = $self->param('efa');
+ my $trip_id = $self->stash('tripid');
+
+ my $stopseq;
+ if ( $trip_id
+ =~ m{ ^ ([^@]*) @ ([^@]*) [(] ([^T]*) T ([^)]*) [)] (.*) $ }x )
+ {
+ $stopseq = {
+ stateless => $1,
+ stop_id => $2,
+ date => $3,
+ time => $4,
+ key => $5
+ };
+ }
+ else {
+ $self->render(
+ '_error',
+ error => "cannot parse trip ID: $trip_id",
+ );
+ return;
+ }
+
+ $self->efa->get_polyline_p(
+ stopseq => $stopseq,
+ service => $backend
+ )->then(
+ sub {
+ my ($trip) = @_;
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ my @polyline = $trip->polyline( fallback => 1 );
+ my @route = $trip->route;
+
+ my $ref_route = [
+ map {
+ {
+ name => $_->full_name,
+ platform => $_->platform,
+ arr => $_->arr,
+ dep => $_->dep,
+ arr_delay => $_->arr_delay,
+ dep_delay => $_->dep_delay,
+ lat => $_->latlon->[0],
+ lon => $_->latlon->[1]
+ }
+ } @route
+ ];
+
+ for my $pl (@polyline) {
+ if ( $pl->{stop} ) {
+ $pl->{name} = $pl->{stop}->full_name;
+ }
+ }
+
+ my $train_pos = $self->estimate_train_positions2(
+ now => $now,
+ route => $ref_route,
+ polyline => \@polyline,
+ );
+
+ $self->render(
+ '_map_infobox',
+ ajax_req => "${trip_id}/0",
+ ajax_route => route_to_ajax( @{$ref_route} ),
+ ajax_polyline => join( '|',
+ map { join( ';', @{$_} ) } @{ $train_pos->{positions} } ),
+ origin => {
+ name => ( $trip->route )[0]->full_name,
+ ts => ( $trip->route )[0]->dep,
+ },
+ destination => {
+ name => ( $trip->route )[-1]->full_name,
+ ts => ( $trip->route )[-1]->arr,
+ },
+ train_no => $trip->number
+ ? ( $trip->type // q{} . ' ' . $trip->number )
+ : undef,
+ next_stop => $train_pos->{next_stop},
+ );
+ }
+ )->catch(
+ sub {
+ sub {
+ my ($err) = @_;
+ $self->render(
+ '_error',
+ error => $err,
+ );
+ }
+ }
+ )->wait;
+}
+
+sub ajax_route_dbris {
+ my ($self) = @_;
+ my $trip_id = $self->stash('tripid');
+
+ $self->dbris->get_polyline_p( id => $trip_id )->then(
+ sub {
+ my ($journey) = @_;
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ my @route = $journey->route;
+ my @polyline = $journey->polyline;
+
+ my $train_pos = $self->estimate_train_positions2(
+ now => $now,
+ route => [
+ map {
+ {
+ name => $_->name,
+ arr => $_->arr,
+ dep => $_->dep,
+ arr_delay => $_->arr_delay,
+ dep_delay => $_->dep_delay,
+ lat => $_->lat,
+ lon => $_->lon
+ }
+ } @route
+ ],
+ polyline => \@polyline,
+ );
+
+ $self->render(
+ '_map_infobox',
+ ajax_req => "${trip_id}/0",
+ ajax_route => route_to_ajax(
+ map {
+ {
+ name => $_->name,
+ platform => $_->platform,
+ arr => $_->arr,
+ arr_cancelled => $_->is_cancelled,
+ arr_delay => $_->arr_delay,
+ dep => $_->dep,
+ dep_cancelled => $_->is_cancelled,
+ dep_delay => $_->dep_delay,
+ }
+ } @route
+ ),
+ ajax_polyline => join(
+ '|',
+ map { join( ';', @{$_} ) } @{ $train_pos->{positions} }
+ ),
+ origin => {
+ name => ( $journey->route )[0]->name,
+ ts => ( $journey->route )[0]->dep,
+ },
+ destination => {
+ name => ( $journey->route )[-1]->name,
+ ts => ( $journey->route )[-1]->arr,
+ },
+ train_no => $journey->number
+ ? ( $journey->type . ' ' . $journey->number )
+ : undef,
+ next_stop => $train_pos->{next_stop},
+ platform_type => q{},
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ '_error',
+ error => $err,
+ );
+ }
+ )->wait;
+}
+
+sub ajax_route_motis {
+ my ($self) = @_;
+
+ my $service = $self->param('motis') // 'transitous';
+ my $trip_id = $self->stash('tripid');
+
+ $self->motis->get_polyline_p(
+ service => $service,
+ id => $trip_id,
+ )->then(
+ sub {
+ my ($trip) = @_;
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ my @stopovers = $trip->stopovers;
+ my @polyline = $trip->polyline;
+
+ my $train_pos = $self->estimate_train_positions2(
+ now => $now,
+ route => [
+ map {
+ {
+ name => $_->stop->name,
+ arr => $_->arrival,
+ dep => $_->departure,
+ arr_delay => $_->arrival_delay,
+ dep_delay => $_->departure_delay,
+ lat => $_->stop->lat,
+ lon => $_->stop->lon,
+ }
+ } @stopovers
+ ],
+ polyline => \@polyline,
+ );
+
+ $self->render(
+ '_map_infobox',
+ ajax_req => "${trip_id}/0",
+ ajax_route => route_to_ajax(
+ map {
+ {
+ name => $_->stop->name,
+ platform => $_->track,
+ arr => $_->arrival,
+ arr_cancelled => $_->is_cancelled,
+ arr_delay => $_->arrival_delay,
+ dep => $_->departure,
+ dep_cancelled => $_->is_cancelled,
+ dep_delay => $_->departure_delay,
+ }
+ } @stopovers
+ ),
+ ajax_polyline => join(
+ '|',
+ map { join( ';', @{$_} ) } @{ $train_pos->{positions} }
+ ),
+ origin => {
+ name => ( $trip->stopovers )[0]->stop->name,
+ ts => ( $trip->stopovers )[0]->departure,
+ },
+ destination => {
+ name => ( $trip->stopovers )[-1]->stop->name,
+ ts => ( $trip->stopovers )[-1]->arrival,
+ },
+ train_no => undef, # FIXME
+ next_stop => $train_pos->{next_stop},
+ platform_type => q{},
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ '_error',
+ error => $err,
+ );
+ }
+ )->wait;
+}
+
+sub ajax_route {
+ my ($self) = @_;
+
+ delete $self->stash->{layout};
+
+ $self->render_later;
+
+ if ( $self->param('dbris') ) {
+ return $self->ajax_route_dbris;
+ }
+ if ( $self->param('motis') ) {
+ return $self->ajax_route_motis;
+ }
+ if ( $self->param('efa') ) {
+ return $self->ajax_route_efa;
+ }
+
+ my $trip_id = $self->stash('tripid');
+ my $line_no = $self->stash('lineno');
+ my $hafas = $self->param('hafas');
+
+ my $service = 'ÖBB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
+ $self->hafas->get_polyline_p(
+ id => $trip_id,
+ line => $line_no,
+ service => $service
+ )->then(
+ sub {
+ my ($journey) = @_;
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ my @route = $journey->route;
+ my @polyline = $journey->polyline;
+
+ my $train_pos = $self->estimate_train_positions2(
+ now => $now,
+ route => [
+ map {
+ {
+ name => $_->loc->name,
+ arr => $_->arr,
+ dep => $_->dep,
+ arr_delay => $_->arr_delay,
+ dep_delay => $_->dep_delay,
+ lat => $_->loc->lat,
+ lon => $_->loc->lon
+ }
+ } @route
+ ],
+ polyline => \@polyline,
+ );
+
+ $self->render(
+ '_map_infobox',
+ ajax_req => "${trip_id}/${line_no}",
+ ajax_route => route_to_ajax(
+ map {
+ {
+ name => $_->loc->name,
+ platform => $_->platform,
+ arr => $_->arr,
+ arr_cancelled => $_->arr_cancelled,
+ arr_delay => $_->arr_delay,
+ dep => $_->dep,
+ dep_cancelled => $_->dep_cancelled,
+ dep_delay => $_->dep_delay,
+ }
+ } @route
+ ),
+ ajax_polyline => join(
+ '|',
+ map { join( ';', @{$_} ) } @{ $train_pos->{positions} }
+ ),
+ origin => {
+ name => ( $journey->route )[0]->loc->name,
+ ts => ( $journey->route )[0]->dep,
+ },
+ destination => {
+ name => $journey->route_end,
+ ts => ( $journey->route )[-1]->arr,
+ },
+ train_no => $journey->number
+ ? ( $journey->type . ' ' . $journey->number )
+ : undef,
+ next_stop => $train_pos->{next_stop},
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ '_error',
+ error => $err,
+ );
+ }
+ )->wait;
+}
+
+sub coverage {
+ my ($self) = @_;
+ my $backend = lc( $self->stash('backend') );
+ my $service = $self->stash('service');
+
+ my $coverage = {};
+
+ if ( $backend eq 'efa' ) {
+ $coverage = $self->efa->get_coverage($service);
+ }
+ elsif ( $backend eq 'hafas' ) {
+ $coverage = $self->hafas->get_coverage($service);
+ }
+ elsif ( $backend eq 'motis' ) {
+ $coverage = $self->motis->get_coverage($service);
+ }
+
+ $self->render(
+ 'coverage_map',
+ title => "Abdeckung $service",
+ hide_opts => 1,
+ with_map => 1,
+ coverage => encode_json($coverage),
+ );
+}
+
+1;
diff --git a/lib/DBInfoscreen/Controller/Static.pm b/lib/DBInfoscreen/Controller/Static.pm
new file mode 100644
index 0000000..9a57f05
--- /dev/null
+++ b/lib/DBInfoscreen/Controller/Static.pm
@@ -0,0 +1,47 @@
+package DBInfoscreen::Controller::Static;
+
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'Mojolicious::Controller';
+
+my %default = (
+ mode => 'app',
+ admode => 'deparr',
+);
+
+sub geostop {
+ my ($self) = @_;
+
+ $self->render(
+ 'geostop',
+ with_geostop => 1,
+ hide_opts => 1,
+ hide_footer => 1,
+ );
+}
+
+sub about {
+ my ($self) = @_;
+
+ $self->render(
+ 'about',
+ hide_opts => 1,
+ hide_footer => 1,
+ );
+}
+
+sub privacy {
+ my ($self) = @_;
+
+ $self->render( 'privacy', hide_opts => 1, hide_footer => 1 );
+}
+
+sub imprint {
+ my ($self) = @_;
+
+ $self->render( 'imprint', hide_opts => 1, hide_footer => 1 );
+}
+
+1;
diff --git a/lib/DBInfoscreen/Controller/Stationboard.pm b/lib/DBInfoscreen/Controller/Stationboard.pm
new file mode 100644
index 0000000..3e07f90
--- /dev/null
+++ b/lib/DBInfoscreen/Controller/Stationboard.pm
@@ -0,0 +1,3052 @@
+package DBInfoscreen::Controller::Stationboard;
+
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'Mojolicious::Controller';
+
+use DateTime;
+use DateTime::Format::Strptime;
+use Encode qw(decode encode);
+use File::Slurp qw(read_file write_file);
+use List::Util qw(max uniq);
+use List::UtilsBy qw(uniq_by);
+use List::MoreUtils qw();
+use Mojo::JSON qw(decode_json encode_json);
+use Mojo::Promise;
+use Mojo::UserAgent;
+use Travel::Status::DE::DBRIS;
+use Travel::Status::DE::DBRIS::Formation;
+use Travel::Status::DE::EFA;
+use Travel::Status::DE::HAFAS;
+use Travel::Status::DE::IRIS;
+use Travel::Status::DE::IRIS::Stations;
+use XML::LibXML;
+
+use utf8;
+
+my %default = (
+ mode => 'app',
+ admode => 'deparr',
+);
+
+sub class_to_product {
+ my ( $self, $hafas ) = @_;
+
+ my $bits = $hafas->get_active_service->{productbits};
+ my $ret;
+
+ for my $i ( 0 .. $#{$bits} ) {
+ $ret->{ 2**$i }
+ = ref( $bits->[$i] ) eq 'ARRAY' ? $bits->[$i][0] : $bits->[$i];
+ }
+
+ return $ret;
+}
+
+sub handle_no_results {
+ my ( $self, $station, $data, $hafas, $efa ) = @_;
+
+ my $errstr = $data->{errstr};
+
+ if ($efa) {
+ if ( $errstr =~ m{ambiguous} and $efa->name_candidates ) {
+ $self->render(
+ 'landingpage',
+ stationlist => [ $efa->name_candidates ],
+ hide_opts => 0,
+ status => $data->{status} // 300,
+ );
+ }
+ else {
+ $self->render(
+ 'landingpage',
+ error => ( $errstr // "Keine Abfahrten an '$station'" ),
+ hide_opts => 0,
+ status => $data->{status} // 404,
+ );
+ }
+ return;
+ }
+ elsif ($hafas) {
+ $self->render_later;
+ my $service = 'ÖBB';
+ if ( $hafas ne '1' and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+ Travel::Status::DE::HAFAS->new_p(
+ locationSearch => $station,
+ service => $service,
+ promise => 'Mojo::Promise',
+ user_agent => $service eq 'PKP' ? Mojo::UserAgent->new : $self->ua,
+ )->then(
+ sub {
+ my ($status) = @_;
+ my @candidates = $status->results;
+ @candidates = map { [ $_->name, $_->eva ] } @candidates;
+ if ( @candidates == 1 and $candidates[0][0] ne $station ) {
+ my $s = $candidates[0][0];
+ my $params = $self->req->params->to_string;
+ $self->redirect_to("/${s}?${params}");
+ return;
+ }
+ for my $candidate (@candidates) {
+ $candidate->[0] =~ s{[&]#x0028;}{(}g;
+ $candidate->[0] =~ s{[&]#x0029;}{)}g;
+ }
+ my $err;
+ if ( not $errstr =~ m{LOCATION} ) {
+ $err = $errstr;
+ }
+ $self->render(
+ 'landingpage',
+ error => $err,
+ stationlist => \@candidates,
+ hide_opts => 0,
+ status => $data->{status} // 300,
+ );
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ 'landingpage',
+ error => ( $err // "Keine Abfahrten an '$station'" ),
+ hide_opts => 0,
+ status => $data->{status} // 500,
+ );
+ return;
+ }
+ )->wait;
+ return;
+ }
+
+ my @candidates = map { [ $_->[1], $_->[0] ] }
+ Travel::Status::DE::IRIS::Stations::get_station($station);
+ if (
+ @candidates > 1
+ or ( @candidates == 1
+ and $candidates[0][0] ne $station
+ and $candidates[0][1] ne $station )
+ )
+ {
+ $self->render(
+ 'landingpage',
+ stationlist => \@candidates,
+ hide_opts => 0,
+ status => $data->{status} // 300,
+ );
+ return;
+ }
+ if ( $data->{station_ds100} and $data->{station_ds100} =~ m{ ^ [OPQXYZ] }x )
+ {
+ $self->render(
+ 'landingpage',
+ error => ( $errstr // "Keine Abfahrten an '$station'" )
+ . '. Das von DBF genutzte IRIS-Backend unterstützt im Regelfall nur innerdeutsche Zugfahrten.',
+ hide_opts => 0,
+ status => $data->{status} // 200,
+ );
+ return;
+ }
+ $self->render(
+ 'landingpage',
+ error => ( $errstr // "Keine Abfahrten an '$station'" ),
+ hide_opts => 0,
+ status => $data->{status} // 404,
+ );
+ return;
+}
+
+sub handle_no_results_json {
+ my ( $self, $station, $data, $api_version ) = @_;
+
+ my $errstr = $data->{errstr};
+ my $callback = $self->param('callback');
+
+ $self->res->headers->access_control_allow_origin(q{*});
+ my $json;
+ if ($errstr) {
+ $json = {
+ api_version => $api_version,
+ error => $errstr,
+ };
+ }
+ else {
+ my @candidates = map { { code => $_->[0], name => $_->[1] } }
+ Travel::Status::DE::IRIS::Stations::get_station($station);
+ if ( @candidates > 1
+ or ( @candidates == 1 and $candidates[0]{code} ne $station ) )
+ {
+ $json = {
+ api_version => $api_version,
+ error => 'ambiguous station code/name',
+ candidates => \@candidates,
+ };
+ }
+ else {
+ $json = {
+ api_version => $api_version,
+ error => ( $errstr // "Got no results for '$station'" )
+ };
+ }
+ }
+ if ($callback) {
+ $json = $self->render_to_string( json => $json );
+ $self->render(
+ data => "$callback($json);",
+ format => 'json',
+ );
+ }
+ else {
+ $self->render(
+ json => $json,
+ status => $data->{status} // 300,
+ );
+ }
+ return;
+}
+
+sub result_is_train {
+ my ( $result, $train ) = @_;
+
+ if ( $train eq $result->type . ' ' . $result->train_no ) {
+ return 1;
+ }
+ return 0;
+}
+
+sub result_has_line {
+ my ( $result, @lines ) = @_;
+ my $line = $result->line;
+
+ if ( List::MoreUtils::any { $line =~ m{^$_} } @lines ) {
+ return 1;
+ }
+ return 0;
+}
+
+sub result_has_platform {
+ my ( $result, @platforms ) = @_;
+ my $platform = ( split( qr{ }, $result->platform // '' ) )[0] // '';
+
+ if ( List::MoreUtils::any { $_ eq $platform } @platforms ) {
+ return 1;
+ }
+ return 0;
+}
+
+sub result_has_train_type {
+ my ( $result, @train_types ) = @_;
+ my $train_type = $result->type;
+
+ if ( List::MoreUtils::any { $train_type =~ m{^$_} } @train_types ) {
+ return 1;
+ }
+ return 0;
+}
+
+sub result_has_via {
+ my ( $result, $via ) = @_;
+
+ my @route;
+
+ if ( $result->isa('Travel::Status::DE::IRIS::Result') ) {
+ @route = ( $result->route_post, $result->sched_route_post );
+ }
+ elsif ( $result->isa('Travel::Status::DE::HAFAS::Journey') ) {
+ @route = map { $_->loc->name } $result->route;
+ }
+ elsif ( $result->isa('Travel::Status::DE::EFA::Departure') ) {
+ @route = map { $_->full_name } $result->route_post;
+ }
+ my $eq_result = List::MoreUtils::any { lc eq lc($via) } @route;
+
+ if ($eq_result) {
+ return 1;
+ }
+
+ my ( $re1_result, $re2_result );
+
+ eval {
+ $re2_result = List::MoreUtils::any { m{\Q$via\E}i } @route;
+ };
+ eval {
+ $re1_result = List::MoreUtils::any { m{$via}i } @route;
+ };
+
+ if ($@) {
+ return $re2_result || $eq_result;
+ }
+
+ return $re1_result || $re2_result || $eq_result;
+}
+
+sub log_api_access {
+ my ($suffix) = @_;
+ $suffix //= q{};
+
+ my $file = "$ENV{DBFAKEDISPLAY_STATS}${suffix}";
+ my $counter = 1;
+ if ( -r $file ) {
+ $counter = read_file($file) + 1;
+ }
+ write_file( $file, $counter );
+ return;
+}
+
+sub json_route_diff {
+ my ( $self, $route, $sched_route ) = @_;
+ my @json_route;
+ my @route = @{$route};
+ my @sched_route = @{$sched_route};
+
+ my $route_idx = 0;
+ my $sched_idx = 0;
+
+ while ( $route_idx <= $#route and $sched_idx <= $#sched_route ) {
+ if ( $route[$route_idx] eq $sched_route[$sched_idx] ) {
+ push( @json_route, { name => $route[$route_idx] } );
+ $route_idx++;
+ $sched_idx++;
+ }
+
+ # this branch is inefficient, but won't be taken frequently
+ elsif (
+ not(
+ List::MoreUtils::any { $route[$route_idx] eq $_ }
+ @sched_route
+ )
+ )
+ {
+ push(
+ @json_route,
+ {
+ name => $route[$route_idx],
+ isAdditional => 1
+ }
+ );
+ $route_idx++;
+ }
+ else {
+ push(
+ @json_route,
+ {
+ name => $sched_route[$sched_idx],
+ isCancelled => 1
+ }
+ );
+ $sched_idx++;
+ }
+ }
+ while ( $route_idx <= $#route ) {
+ push(
+ @json_route,
+ {
+ name => $route[$route_idx],
+ isAdditional => 1,
+ isCancelled => 0
+ }
+ );
+ $route_idx++;
+ }
+ while ( $sched_idx <= $#sched_route ) {
+ push(
+ @json_route,
+ {
+ name => $sched_route[$sched_idx],
+ isAdditional => 0,
+ isCancelled => 1
+ }
+ );
+ $sched_idx++;
+ }
+ return @json_route;
+}
+
+sub get_results_p {
+ my ( $self, $station, %opt ) = @_;
+ my $data;
+
+ if ( $opt{dbris} ) {
+ if ( $station =~ m{ [@] L = (?<eva> \d+ ) [@] }x ) {
+ return Travel::Status::DE::DBRIS->new_p(
+ station => {
+ eva => $+{eva},
+ id => $station,
+ },
+ cache => $opt{cache_iris_rt},
+ lwp_options => {
+ timeout => 10,
+ agent => 'dbf.finalrewind.org/2'
+ },
+ promise => 'Mojo::Promise',
+ user_agent => Mojo::UserAgent->new,
+ );
+ }
+ my $promise = Mojo::Promise->new;
+ Travel::Status::DE::DBRIS->new_p(
+ locationSearch => $station,
+ cache => $opt{cache_iris_main},
+ lwp_options => {
+ timeout => 10,
+ agent => 'dbf.finalrewind.org/2'
+ },
+ promise => 'Mojo::Promise',
+ user_agent => Mojo::UserAgent->new,
+ )->then(
+ sub {
+ my ($dbris) = @_;
+ $promise->reject( 'station disambiguation', $dbris );
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $promise->reject("'$err' while trying to look up '$station'");
+ return;
+ }
+ )->wait;
+ return $promise;
+ }
+ if ( $opt{efa} ) {
+ my $service = 'VRR';
+ if ( $opt{efa} ne '1'
+ and Travel::Status::DE::EFA::get_service( $opt{efa} ) )
+ {
+ $service = $opt{efa};
+ }
+ return Travel::Status::DE::EFA->new_p(
+ service => $service,
+ name => $station,
+ full_routes => 1,
+ cache => $opt{cache_iris_rt},
+ lwp_options => {
+ timeout => 10,
+ agent => 'dbf.finalrewind.org/2'
+ },
+ promise => 'Mojo::Promise',
+ user_agent => Mojo::UserAgent->new,
+ );
+ }
+ if ( $opt{hafas} ) {
+ my $service = 'ÖBB';
+ if ( $opt{hafas} ne '1'
+ and Travel::Status::DE::HAFAS::get_service( $opt{hafas} ) )
+ {
+ $service = $opt{hafas};
+ }
+ return Travel::Status::DE::HAFAS->new_p(
+ service => $service,
+ station => $station,
+ arrivals => $opt{arrivals},
+ cache => $opt{cache_iris_rt},
+ lwp_options => {
+ timeout => 10,
+ agent => 'dbf.finalrewind.org/2'
+ },
+ promise => 'Mojo::Promise',
+ user_agent => $service eq 'PKP' ? Mojo::UserAgent->new : $self->ua,
+ );
+ }
+
+ if ( $ENV{DBFAKEDISPLAY_STATS} ) {
+ log_api_access();
+ }
+
+ # requests with DS100 codes should be preferred (they avoid
+ # encoding problems on the IRIS server). However, only use them
+ # if we have an exact match. Ask the backend otherwise.
+ my @station_matches
+ = Travel::Status::DE::IRIS::Stations::get_station($station);
+
+ # Requests with EVA codes can be handled even if we do not know about them.
+ if ( @station_matches != 1 and $station =~ m{^\d+$} ) {
+ @station_matches = ( [ undef, undef, $station ] );
+ }
+
+ if ( @station_matches == 1 ) {
+ $station = $station_matches[0][2];
+ return Travel::Status::DE::IRIS->new_p(
+ iris_base => $ENV{DBFAKEDISPLAY_IRIS_BASE},
+ station => $station,
+ main_cache => $opt{cache_iris_main},
+ realtime_cache => $opt{cache_iris_rt},
+ log_dir => $ENV{DBFAKEDISPLAY_XMLDUMP_DIR},
+ lookbehind => 20,
+ lwp_options => {
+ timeout => 10,
+ agent => 'dbf.finalrewind.org/2'
+ },
+ promise => 'Mojo::Promise',
+ user_agent => Mojo::UserAgent->new,
+ get_station => \&Travel::Status::DE::IRIS::Stations::get_station,
+ meta => Travel::Status::DE::IRIS::Stations::get_meta(),
+ %opt
+ );
+ }
+ elsif ( @station_matches > 1 ) {
+ return Mojo::Promise->reject('Ambiguous station name');
+ }
+ else {
+ return Mojo::Promise->reject('Unknown station name');
+ }
+}
+
+sub handle_board_request {
+ my ($self) = @_;
+ my $station = $self->stash('station');
+
+ my $template = $self->param('mode') // 'app';
+ my $dbris = $self->param('dbris');
+ my $efa = $self->param('efa');
+ my $hafas = $self->param('hafas');
+ my $with_related = !$self->param('no_related');
+ my %opt = (
+ cache_iris_main => $self->app->cache_iris_main,
+ cache_iris_rt => $self->app->cache_iris_rt,
+ lookahead => $self->config->{lookahead},
+ dbris => $dbris,
+ efa => $efa,
+ hafas => $hafas,
+ );
+
+ if ( $self->param('past') ) {
+ $opt{datetime} = DateTime->now( time_zone => 'Europe/Berlin' )
+ ->subtract( minutes => 60 );
+ $opt{lookahead} += 60;
+ }
+
+ if ( $self->param('admode') and $self->param('admode') eq 'arr' ) {
+ $opt{arrivals} = 1;
+ }
+
+ my $api_version = $Travel::Status::DE::IRIS::VERSION;
+
+ $self->stash( departures => [] );
+ $self->stash( title => 'DBF' );
+
+ if (
+ not(
+ List::MoreUtils::any { $template eq $_ }
+ (qw(app infoscreen json multi single text))
+ )
+ )
+ {
+ $template = 'app';
+ }
+
+ if ( defined $station and $station =~ s{ [.] txt $ }{}x ) {
+ $template = 'text';
+ $self->param( station => $station );
+ $self->stash( layout => 'text' );
+ }
+ elsif ( defined $station and $station =~ s{ [.] json $ }{}x ) {
+ $template = 'json';
+ }
+ elsif ( $template ne 'app' ) {
+ $self->stash( layout => 'legacy' );
+ }
+
+ # Historically, there were two JSON APIs: 'json' (undocumented, raw
+ # passthrough of serialized Travel::Status::DE::IRIS::Result /
+ # Travel::Status::DE::DE::HAFAS::Result objects) and 'marudor'
+ # (documented, IRIS only, stable versioned API). The latter was initially
+ # created for marudor.de, but quickly used by other clients as well.
+ #
+ # marudor.de switched to a nodejs IRIS parser in December 2018. As the
+ # 'json' API was not used and the 'marudor' variant is no longer related to
+ # (or used by) marudor.de, it was renamed to 'json'. Many clients won't
+ # notice this for year to come, so we make sure mode=marudor still works as
+ # intended.
+ if (
+ $template eq 'marudor'
+ or ( $self->req->headers->accept
+ and $self->req->headers->accept eq 'application/json' )
+ )
+ {
+ $template = 'json';
+ }
+
+ $self->param( mode => $template );
+
+ if ( not $station ) {
+ $self->param( rt => 1 );
+ $self->render( 'landingpage', show_intro => 1 );
+ return;
+ }
+
+ # pre-fill station / train input form
+ $self->stash( input => $station );
+ $self->param( input => $station );
+
+ if ($with_related) {
+ $opt{with_related} = 1;
+ }
+
+ if ( $self->param('train') and not $opt{datetime} ) {
+
+ # request results from twenty minutes ago to avoid train details suddenly
+ # becoming unavailable when its scheduled departure is reached.
+ $opt{datetime} = DateTime->now( time_zone => 'Europe/Berlin' )
+ ->subtract( minutes => 20 );
+ $opt{lookahead} = $self->config->{lookahead} + 20;
+ }
+
+ $self->render_later;
+
+ $self->get_results_p( $station, %opt )->then(
+ sub {
+ my ($status) = @_;
+ if ($dbris) {
+ $self->render_board_dbris( $station, $status );
+ return;
+ }
+ if ($efa) {
+ $self->render_board_efa( $station, $status );
+ return;
+ }
+ my $data = {
+ results => [ $status->results ],
+ hafas => $hafas ? $status : undef,
+ station_ds100 =>
+ ( $status->station ? $status->station->{ds100} : undef ),
+ station_eva => (
+ $status->station
+ ? ( $status->station->{uic} // $status->station->{eva} )
+ : undef
+ ),
+ station_evas =>
+ ( $status->station ? $status->station->{evas} : [] ),
+ station_name =>
+ ( $status->station ? $status->station->{name} : $station ),
+ };
+
+ if ( not @{ $data->{results} } and $template eq 'json' ) {
+ $self->handle_no_results_json( $station, $data, $api_version );
+ return;
+ }
+ if ( not @{ $data->{results} } ) {
+ $self->handle_no_results( $station, $data, $hafas );
+ return;
+ }
+ $self->render_board_hafas($data);
+ }
+ )->catch(
+ sub {
+ my ( $err, $status ) = @_;
+ if ( $dbris and $err eq 'station disambiguation' ) {
+ for my $result ( $status->results ) {
+ if ( defined $result->eva ) {
+ $self->redirect_to(
+ '/' . $result->id . '?dbris=bahn.de' );
+ return;
+ }
+ }
+ }
+ if ( $template eq 'json' ) {
+ $self->handle_no_results_json(
+ $station,
+ {
+ errstr => $err,
+ status =>
+ ( $err =~ m{[Aa]mbiguous|LOCATION} ? 300 : 500 ),
+ },
+ $api_version
+ );
+ return;
+ }
+ $self->handle_no_results(
+ $station,
+ {
+ errstr => $err,
+ status => ( $err =~ m{[Aa]mbiguous|LOCATION} ? 300 : 500 ),
+ },
+ $hafas,
+ $efa ? $status : undef
+ );
+ return;
+ }
+ )->wait;
+}
+
+sub filter_results {
+ my ( $self, @results ) = @_;
+
+ if ( my $train = $self->param('train') ) {
+ @results = grep { result_is_train( $_, $train ) } @results;
+ }
+
+ if ( my @lines = split( /,/, $self->param('lines') // q{} ) ) {
+ @results = grep { result_has_line( $_, @lines ) } @results;
+ }
+
+ if ( my @platforms = split( /,/, $self->param('platforms') // q{} ) ) {
+ @results = grep { result_has_platform( $_, @platforms ) } @results;
+ }
+
+ if ( my $via = $self->param('via') ) {
+ $via =~ s{ , \s* }{|}gx;
+ @results = grep { result_has_via( $_, $via ) } @results;
+ }
+
+ if ( my @train_types = split( /,/, $self->param('train_types') // q{} ) ) {
+ @results = grep { result_has_train_type( $_, @train_types ) } @results;
+ }
+
+ if ( my $limit = $self->param('limit') ) {
+ if ( $limit =~ m{ ^ \d+ $ }x ) {
+ splice( @results, $limit );
+ }
+ }
+
+ return @results;
+}
+
+sub format_iris_result_info {
+ my ( $self, $template, $result ) = @_;
+ my ( $info, $moreinfo );
+
+ my $delaymsg
+ = join( ', ', map { $_->[1] } $result->delay_messages );
+ my $qosmsg = join( ' +++ ', map { $_->[1] } $result->qos_messages );
+ if ( $result->is_cancelled ) {
+ $info = "Fahrt fällt aus";
+ if ($delaymsg) {
+ $info .= ": ${delaymsg}";
+ }
+ }
+ elsif ( $result->departure_is_cancelled ) {
+ $info = "Zug endet hier";
+ if ($delaymsg) {
+ $info .= ": ${delaymsg}";
+ }
+ }
+ elsif ( $result->delay and $result->delay >= 20 ) {
+ if ( $template eq 'app' or $template eq 'infoscreen' ) {
+ $info = $delaymsg;
+ }
+ else {
+ $info = sprintf( 'ca. +%d%s%s',
+ $result->delay, $delaymsg ? q{: } : q{}, $delaymsg );
+ }
+ }
+ if ( $result->replacement_for
+ and $template ne 'app'
+ and $template ne 'infoscreen' )
+ {
+ for my $rep ( $result->replacement_for ) {
+ $info = sprintf(
+ 'Ersatzzug für %s %s %s%s',
+ $rep->type, $rep->train_no,
+ $info ? '+++ ' : q{}, $info // q{}
+ );
+ }
+ }
+ if ( $info and $qosmsg ) {
+ $info .= ' +++ ';
+ }
+ $info .= $qosmsg;
+
+ if ( $result->additional_stops and not $result->is_cancelled ) {
+ my $additional_line = join( q{, }, $result->additional_stops );
+ $info
+ = 'Zusätzliche Halte: '
+ . $additional_line
+ . ( $info ? ' +++ ' : q{} )
+ . $info;
+ if ( $template ne 'json' ) {
+ push(
+ @{$moreinfo},
+ [ 'Außerplanmäßiger Halt in', { text => $additional_line } ]
+ );
+ }
+ }
+
+ if ( $result->canceled_stops and not $result->is_cancelled ) {
+ my $cancel_line = join( q{, }, $result->canceled_stops );
+ $info
+ = 'Ohne Halt in: ' . $cancel_line . ( $info ? ' +++ ' : q{} ) . $info;
+ if ( $template ne 'json' ) {
+ push( @{$moreinfo}, [ 'Ohne Halt in', { text => $cancel_line } ] );
+ }
+ }
+
+ push( @{$moreinfo}, $result->messages );
+
+ return ( $info, $moreinfo );
+}
+
+sub render_train {
+ my ( $self, $result, $departure, $station_name, $template ) = @_;
+
+ $departure->{links} = [];
+ if ( $result->can('route_pre') ) {
+ $departure->{route_pre_diff} = [
+ $self->json_route_diff(
+ [ $result->route_pre ],
+ [ $result->sched_route_pre ]
+ )
+ ];
+ $departure->{route_post_diff} = [
+ $self->json_route_diff(
+ [ $result->route_post ],
+ [ $result->sched_route_post ]
+ )
+ ];
+ }
+
+ if ( not $result->has_realtime ) {
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+ if ( $result->start < $now ) {
+ $departure->{missing_realtime} = 1;
+ }
+ else {
+ $departure->{no_realtime_yet} = 1;
+ }
+ }
+
+ my $linetype = 'bahn';
+
+ if ( $result->can('classes') ) {
+ my @classes = $result->classes;
+ if ( @classes == 0 ) {
+ $linetype = 'ext';
+ }
+ elsif ( grep { $_ eq 'S' } @classes ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( grep { $_ eq 'F' } @classes ) {
+ $linetype = 'fern';
+ }
+ }
+ elsif ( $result->can('class') ) {
+ if ( $result->class <= 2 ) {
+ $linetype = 'fern';
+ }
+ elsif ( $result->class == 16 ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $result->class == 32 ) {
+ $linetype = 'bus';
+ }
+ elsif ( $result->class == 128 ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $result->class == 256 ) {
+ $linetype = 'tram';
+ }
+ }
+
+ $self->render_later;
+
+ my $wagonorder_req = Mojo::Promise->new;
+ my $occupancy_req = Mojo::Promise->new;
+ my $stationinfo_req = Mojo::Promise->new;
+ my $route_req = Mojo::Promise->new;
+
+ my @requests
+ = ( $wagonorder_req, $occupancy_req, $stationinfo_req, $route_req );
+
+ if ( $departure->{wr_dt} ) {
+ $self->wagonorder->get_p(
+ train_type => $result->type,
+ train_number => $result->train_no,
+ datetime => $departure->{wr_dt},
+ eva => $departure->{eva}
+ )->then(
+ sub {
+ my ( $wr_json, $wr_param ) = @_;
+ eval {
+ my $wr
+ = Travel::Status::DE::DBRIS::Formation->new(
+ json => $wr_json );
+ $departure->{wr} = $wr;
+ $departure->{wr_link} = join( '&',
+ map { $_ . '=' . $wr_param->{$_} } keys %{$wr_param} );
+ $departure->{wr_text} = join( q{ • },
+ map { $_->desc_short }
+ grep { $_->desc_short } $wr->groups );
+ my $first = 0;
+ for my $group ( $wr->groups ) {
+ my $had_entry = 0;
+ for my $wagon ( $group->carriages ) {
+ if (
+ not( $wagon->is_locomotive
+ or $wagon->is_powercar )
+ )
+ {
+ my $class;
+ if ($first) {
+ push(
+ @{ $departure->{wr_preview} },
+ [ '•', 'meta' ]
+ );
+ $first = 0;
+ }
+ my $entry;
+ if ( $wagon->is_closed ) {
+ $entry = 'X';
+ $class = 'closed';
+ }
+ elsif ( $wagon->number ) {
+ $entry = $wagon->number;
+ }
+ else {
+ if ( $wagon->has_first_class ) {
+ if ( $wagon->has_second_class ) {
+ $entry = '½';
+ }
+ else {
+ $entry = '1.';
+ }
+ }
+ elsif ( $wagon->has_second_class ) {
+ $entry = '2.';
+ }
+ else {
+ $entry = $wagon->type;
+ }
+ }
+ if (
+ $group->train_no ne $departure->{train_no} )
+ {
+ $class = 'otherno';
+ }
+ push(
+ @{ $departure->{wr_preview} },
+ [ $entry, $class ]
+ );
+ $had_entry = 1;
+ }
+ }
+ if ($had_entry) {
+ $first = 1;
+ }
+ }
+ };
+ $departure->{wr_text} ||= 'Wagen';
+ return;
+ },
+ sub {
+ $departure->{wr_dt} = undef;
+ return;
+ }
+ )->finally(
+ sub {
+ $wagonorder_req->resolve;
+ return;
+ }
+ )->wait;
+ }
+ else {
+ $wagonorder_req->resolve;
+ }
+
+ $self->efa->get_efa_occupancy(
+ eva => $result->station_uic,
+ train_no => $result->train_no
+ )->then(
+ sub {
+ my ($occupancy) = @_;
+ $departure->{occupancy} = $occupancy;
+ return;
+ },
+ sub {
+ $departure->{occupancy} = undef;
+ return;
+ }
+ )->finally(
+ sub {
+ $occupancy_req->resolve;
+ return;
+ }
+ )->wait;
+
+ $self->wagonorder->get_stationinfo_p( $result->station_uic )->then(
+ sub {
+ my ($station_info) = @_;
+ my ($platform_number) = ( $result->platform =~ m{(\d+)} );
+ if ( not defined $platform_number ) {
+ return;
+ }
+ my $platform_info = $station_info->{$platform_number};
+ if ( not $platform_info ) {
+ return;
+ }
+ my $prev_stop = ( $result->route_pre )[-1];
+ my $next_stop = ( $result->route_post )[0];
+ my $direction;
+
+ if ( $platform_info->{kopfgleis} and $next_stop ) {
+ $direction = $platform_info->{direction} eq 'r' ? 'l' : 'r';
+ }
+ elsif ( $platform_info->{kopfgleis} ) {
+ $direction = $platform_info->{direction};
+ }
+ elsif ( $prev_stop
+ and exists $platform_info->{direction_from}{$prev_stop} )
+ {
+ $direction = $platform_info->{direction_from}{$prev_stop};
+ }
+ elsif ( $next_stop
+ and exists $platform_info->{direction_from}{$next_stop} )
+ {
+ $direction
+ = $platform_info->{direction_from}{$next_stop} eq 'r'
+ ? 'l'
+ : 'r';
+ }
+
+ if ($direction) {
+ $departure->{wr_direction} = $direction;
+ $departure->{wr_direction_num} = $direction eq 'l' ? 0 : 100;
+ }
+ elsif ( $platform_info->{direction} ) {
+ $departure->{wr_direction} = 'a' . $platform_info->{direction};
+ $departure->{wr_direction_num}
+ = $platform_info->{direction} eq 'l' ? 0 : 100;
+ }
+
+ return;
+ },
+ sub {
+ # errors don't matter here
+ return;
+ }
+ )->finally(
+ sub {
+ $stationinfo_req->resolve;
+ return;
+ }
+ )->wait;
+
+ my %opt = ( train => $result );
+
+ #if ( $self->languages =~ m{^en} ) {
+ # $opt{language} = 'en';
+ #}
+
+ $self->hafas->get_route_p(%opt)->then(
+ sub {
+ my ( $route, $journey ) = @_;
+
+ $departure->{trip_id} = $journey->id;
+ $departure->{operators} = [ $journey->operators ];
+ $departure->{date} = $route->[0]{sched_dep} // $route->[0]{dep};
+
+ # Use HAFAS route as source of truth; ignore IRIS data
+ $departure->{route_pre_diff} = [];
+ $departure->{route_post_diff} = $route;
+ my $split;
+ for my $i ( 0 .. $#{ $departure->{route_post_diff} } ) {
+ if ( $departure->{route_post_diff}[$i]{name} eq $station_name )
+ {
+ $split = $i;
+ if ( my $load = $route->[$i]{load} ) {
+ if ( %{$load} ) {
+ $departure->{utilization}
+ = [ $load->{FIRST}, $load->{SECOND} ];
+ }
+ }
+ $departure->{tz_offset} = $route->[$i]{tz_offset};
+ $departure->{local_dt_da} = $route->[$i]{local_dt_da};
+ $departure->{local_sched_arr}
+ = $route->[$i]{local_sched_arr};
+ $departure->{local_sched_dep}
+ = $route->[$i]{local_sched_dep};
+ $departure->{is_annotated} = $route->[$i]{is_annotated};
+ $departure->{prod_name} = $route->[$i]{prod_name};
+ $departure->{direction} = $route->[$i]{direction};
+ $departure->{operator} = $route->[$i]{operator};
+ last;
+ }
+ }
+
+ if ( defined $split ) {
+ for my $i ( 0 .. $split - 1 ) {
+ push(
+ @{ $departure->{route_pre_diff} },
+ shift( @{ $departure->{route_post_diff} } )
+ );
+ }
+
+ # remove entry for $station_name
+ shift( @{ $departure->{route_post_diff} } );
+ }
+
+ my @him_messages;
+ my @him_details;
+ for my $message ( $journey->messages ) {
+ if ( $message->code ) {
+ push( @him_details,
+ [ $message->short // q{}, { text => $message->text } ]
+ );
+ }
+ else {
+ push( @him_messages,
+ [ $message->short // q{}, { text => $message->text } ]
+ );
+ }
+ }
+ for my $m (@him_messages) {
+ if ( $m->[0] =~ s{: Information.}{:} ) {
+ $m->[1]{icon} = 'info_outline';
+ }
+ elsif ( $m->[0] =~ s{: Störung.}{: } ) {
+ $m->[1]{icon} = 'warning';
+ }
+ elsif ( $m->[0] =~ s{: Bauarbeiten.}{: } ) {
+ $m->[1]{icon} = 'build';
+ }
+ $m->[0] =~ s{(?!<)->}{ → };
+ }
+ unshift( @{ $departure->{moreinfo} }, @him_messages );
+ unshift( @{ $departure->{details} }, @him_details );
+ }
+ )->catch(
+ sub {
+ # nop
+ }
+ )->finally(
+ sub {
+ $route_req->resolve;
+ return;
+ }
+ )->wait;
+
+ # Defer rendering until all requests have completed
+ Mojo::Promise->all(@requests)->then(
+ sub {
+ $self->respond_to(
+ json => {
+ json => {
+ departure => $departure,
+ station_name => $station_name,
+ },
+ },
+ any => {
+ template => $template // '_train_details',
+ description => sprintf(
+ '%s %s%s%s nach %s',
+ $departure->{train_type},
+ $departure->{train_line} // $departure->{train_no},
+ $departure->{origin} ? ' von ' : q{},
+ $departure->{origin} // q{},
+ $departure->{destination} // 'unbekannt'
+ ),
+ departure => $departure,
+ linetype => $linetype,
+ dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ station_name => $station_name,
+ nav_link =>
+ $self->url_for( 'station', station => $station_name )
+ ->query(
+ {
+ detailed => $self->param('detailed'),
+ hafas => $self->param('hafas')
+ }
+ ),
+ },
+ );
+ }
+ )->wait;
+}
+
+# /z/:train/*station
+sub station_train_details {
+ my ($self) = @_;
+ my $train_no = $self->stash('train');
+ my $station = $self->stash('station');
+
+ if ( $self->param('ajax') ) {
+ delete $self->stash->{layout};
+ }
+
+ if ( $station =~ s{ [.] json $ }{}x ) {
+ $self->stash( format => 'json' );
+ }
+
+ my %opt = (
+ cache_iris_main => $self->app->cache_iris_main,
+ cache_iris_rt => $self->app->cache_iris_rt,
+ );
+
+ my $api_version = $Travel::Status::DE::IRIS::VERSION;
+
+ $self->stash( departures => [] );
+ $self->stash( title => 'DBF' );
+ $self->stash( version => $self->config->{version} );
+
+ if ( $self->param('past') ) {
+ $opt{datetime} = DateTime->now( time_zone => 'Europe/Berlin' )
+ ->subtract( minutes => 80 );
+ $opt{lookahead} = $self->config->{lookahead} + 80;
+ }
+ else {
+ $opt{datetime} = DateTime->now( time_zone => 'Europe/Berlin' )
+ ->subtract( minutes => 20 );
+ $opt{lookahead} = $self->config->{lookahead} + 20;
+ }
+
+ # Berlin Hbf exists twice:
+ # - BLS / 8011160
+ # - BL / 8098160 (formerly "Berlin Hbf (tief)")
+ # Right now DBF assumes that station name -> EVA / DS100 is a unique map.
+ # This is not the case. Work around it here until dbf has been adjusted
+ # properly.
+ if ( $station eq 'Berlin Hbf' ) {
+ $opt{with_related} = 1;
+ }
+
+ $self->render_later;
+
+ # Always performs an IRIS request
+ $self->get_results_p( $station, %opt )->then(
+ sub {
+ my ($status) = @_;
+ my ($result)
+ = grep { result_is_train( $_, $train_no ) } $status->results;
+
+ if ( not $result ) {
+ die("Train not found\n");
+ }
+
+ my ( $info, $moreinfo )
+ = $self->format_iris_result_info( 'app', $result );
+
+ my $result_info = {
+ sched_arrival => $result->sched_arrival
+ ? $result->sched_arrival->strftime('%H:%M')
+ : undef,
+ sched_departure => $result->sched_departure
+ ? $result->sched_departure->strftime('%H:%M')
+ : undef,
+ arrival => $result->arrival
+ ? $result->arrival->strftime('%H:%M')
+ : undef,
+ departure => $result->departure
+ ? $result->departure->strftime('%H:%M')
+ : undef,
+ arrival_hidden => $result->arrival_hidden,
+ departure_hidden => $result->departure_hidden,
+ train_type => $result->type // '',
+ train_line => $result->line_no,
+ train_no => $result->train_no,
+ destination => $result->destination,
+ origin => $result->origin,
+ platform => $result->platform,
+ scheduled_platform => $result->sched_platform,
+ is_cancelled => $result->is_cancelled,
+ departure_is_cancelled => $result->departure_is_cancelled,
+ arrival_is_cancelled => $result->arrival_is_cancelled,
+ moreinfo => $moreinfo,
+ delay => $result->delay,
+ arrival_delay => $result->arrival_delay,
+ departure_delay => $result->departure_delay,
+ route_pre => [ $result->route_pre ],
+ route_post => [ $result->route_post ],
+ replaced_by => [
+ map { $_->type . q{ } . $_->train_no } $result->replaced_by
+ ],
+ replacement_for => [
+ map { $_->type . q{ } . $_->train_no }
+ $result->replacement_for
+ ],
+ wr_dt => $result->sched_departure,
+ eva => $result->station_uic,
+ start => $result->start,
+ };
+
+ $self->stash( title => $status->station->{name}
+ // $self->stash('station') );
+ $self->stash( hide_opts => 1 );
+
+ $self->render_train(
+ $result,
+ $result_info,
+ $status->station->{name} // $self->stash('station'),
+ $self->param('ajax') ? '_train_details' : 'train_details'
+ );
+ }
+ )->catch(
+ sub {
+ my ($errstr) = @_;
+ $self->respond_to(
+ json => {
+ json => {
+ error =>
+"Keine Abfahrt von $train_no in $station gefunden: $errstr",
+ },
+ status => 404,
+ },
+ any => {
+ template => 'landingpage',
+ error =>
+"Keine Abfahrt von $train_no in $station gefunden: $errstr",
+ status => 404,
+ },
+ );
+ return;
+ }
+ )->wait;
+}
+
+sub train_details_dbris {
+ my ($self) = @_;
+ my $trip_id = $self->stash('train');
+
+ $self->render_later;
+
+ $self->dbris->get_journey_p( id => $trip_id )->then(
+ sub {
+ my ($dbris) = @_;
+ my $trip = $dbris->result;
+
+ my ( @him_messages, @him_details );
+ for my $message ( $trip->messages ) {
+ if ( not $message->{ueberschrift} ) {
+ push(
+ @him_messages,
+ [
+ q{},
+ {
+ icon => $message->{prioritaet} eq 'HOCH'
+ ? 'warning'
+ : 'info',
+ text => $message->{text}
+ }
+ ]
+ );
+ }
+ }
+
+ for my $attribute ( $trip->attributes ) {
+ push(
+ @him_details,
+ [
+ q{},
+ {
+ text => $attribute->{value}
+ . (
+ $attribute->{teilstreckenHinweis}
+ ? q { } . $attribute->{teilstreckenHinweis}
+ : q{}
+ )
+ }
+ ]
+ );
+ }
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+ my $res = {
+ trip_id => $trip_id,
+ train_line => $trip->train,
+ train_no => $trip->number,
+ origin => ( $trip->route )[0]->name,
+ destination => ( $trip->route )[-1]->name,
+ operators => [],
+ linetype => 'bahn',
+ route_pre_diff => [],
+ route_post_diff => [],
+ moreinfo => [@him_messages],
+ details => [@him_details],
+ replaced_by => [],
+ replacement_for => [],
+ };
+
+ my $line = $trip->train;
+ if ( $line =~ m{ STR }x ) {
+ $res->{linetype} = 'tram';
+ }
+ elsif ( $line =~ m{ ^ S }x ) {
+ $res->{linetype} = 'sbahn';
+ }
+ elsif ( $line =~ m{ U }x ) {
+ $res->{linetype} = 'ubahn';
+ }
+ elsif ( $line =~ m{ Bus }x ) {
+ $res->{linetype} = 'bus';
+ }
+ elsif ( $line =~ m{ ^ [EI]CE? }x ) {
+ $res->{linetype} = 'fern';
+ }
+ elsif ( $line =~ m{ EST | FLX }x ) {
+ $res->{linetype} = 'ext';
+ }
+
+ my $station_is_past = 1;
+ for my $stop ( $trip->route ) {
+
+ push(
+ @{ $res->{route_post_diff} },
+ {
+ name => $stop->name,
+ eva => $stop->eva,
+ id => $stop->id,
+ sched_arr => $stop->sched_arr,
+ sched_dep => $stop->sched_dep,
+ rt_arr => $stop->rt_arr,
+ rt_dep => $stop->rt_dep,
+ arr_delay => $stop->arr_delay,
+ dep_delay => $stop->dep_delay,
+ platform => $stop->platform,
+ }
+ );
+ if (
+ $station_is_past
+ and $now->epoch < (
+ $res->{route_post_diff}[-1]{rt_arr}
+ // $res->{route_post_diff}[-1]{rt_dep}
+ // $res->{route_post_diff}[-1]{sched_arr}
+ // $res->{route_post_diff}[-1]{sched_dep} // $now
+ )->epoch
+ )
+ {
+ $station_is_past = 0;
+ }
+ $res->{route_post_diff}[-1]{isPast} = $station_is_past;
+ }
+
+ if ( my $req_id = $self->param('highlight') ) {
+ my $split;
+ for my $i ( 0 .. $#{ $res->{route_post_diff} } ) {
+ if ( $res->{route_post_diff}[$i]{eva} eq $req_id ) {
+ $split = $i;
+ last;
+ }
+ }
+ if ( defined $split ) {
+ $self->stash(
+ station_name => $res->{route_post_diff}[$split]{name} );
+ for my $i ( 0 .. $split - 1 ) {
+ push(
+ @{ $res->{route_pre_diff} },
+ shift( @{ $res->{route_post_diff} } )
+ );
+ }
+ my $station_info = shift( @{ $res->{route_post_diff} } );
+ $res->{eva} = $station_info->{eva};
+ if ( $station_info->{sched_arr} ) {
+ $res->{sched_arrival}
+ = $station_info->{sched_arr}->strftime('%H:%M');
+ }
+ if ( $station_info->{rt_arr} ) {
+ $res->{arrival}
+ = $station_info->{rt_arr}->strftime('%H:%M');
+ }
+ if ( $station_info->{sched_dep} ) {
+ $res->{sched_departure}
+ = $station_info->{sched_dep}->strftime('%H:%M');
+ }
+ if ( $station_info->{rt_dep} ) {
+ $res->{departure}
+ = $station_info->{rt_dep}->strftime('%H:%M');
+ }
+ $res->{arrival_is_cancelled}
+ = $station_info->{arr_cancelled};
+ $res->{departure_is_cancelled}
+ = $station_info->{dep_cancelled};
+ $res->{is_cancelled} = $res->{arrival_is_cancelled}
+ || $res->{arrival_is_cancelled};
+ $res->{tz_offset} = $station_info->{tz_offset};
+ $res->{local_dt_da} = $station_info->{local_dt_da};
+ $res->{local_sched_arr} = $station_info->{local_sched_arr};
+ $res->{local_sched_dep} = $station_info->{local_sched_dep};
+ $res->{is_annotated} = $station_info->{is_annotated};
+ $res->{prod_name} = $station_info->{prod_name};
+ $res->{direction} = $station_info->{direction};
+ $res->{operator} = $station_info->{operator};
+ $res->{platform} = $station_info->{platform};
+ $res->{scheduled_platform}
+ = $station_info->{sched_platform};
+ }
+ }
+
+ $self->respond_to(
+ json => {
+ json => {
+ journey => $trip,
+ },
+ },
+ any => {
+ template => $self->param('ajax')
+ ? '_train_details'
+ : 'train_details',
+ description => sprintf(
+ '%s %s%s%s nach %s',
+ $res->{train_type},
+ $res->{train_line} // $res->{train_no},
+ $res->{origin} ? ' von ' : q{},
+ $res->{origin} // q{},
+ $res->{destination} // 'unbekannt'
+ ),
+ departure => $res,
+ linetype => $res->{linetype},
+ dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ },
+ );
+ }
+ )->catch(
+ sub {
+ my ($e) = @_;
+ $self->respond_to(
+ json => {
+ json => {
+ error => $e,
+ },
+ status => 500,
+ },
+ any => {
+ template => 'exception',
+ message => $e,
+ exception => undef,
+ snapshot => {},
+ status => 500,
+ },
+ );
+ }
+ )->wait;
+}
+
+sub train_details_efa {
+ my ($self) = @_;
+ my $trip_id = $self->stash('train');
+
+ my $stopseq;
+ if ( $trip_id
+ =~ m{ ^ ([^@]*) @ ([^@]*) [(] ([^T]*) T ([^)]*) [)] (.*) $ }x )
+ {
+ $stopseq = {
+ stateless => $1,
+ stop_id => $2,
+ date => $3,
+ time => $4,
+ key => $5
+ };
+ }
+ else {
+ $self->render( 'not_found', status => 404 );
+ return;
+ }
+
+ $self->render_later;
+
+ Travel::Status::DE::EFA->new_p(
+ service => $self->param('efa'),
+ stopseq => $stopseq,
+ cache => $self->app->cache_iris_rt,
+ lwp_options => {
+ timeout => 10,
+ agent => 'dbf.finalrewind.org/2'
+ },
+ promise => 'Mojo::Promise',
+ user_agent => Mojo::UserAgent->new,
+ )->then(
+ sub {
+ my ($efa) = @_;
+ my $trip = $efa->result;
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+ my $res = {
+ trip_id => $trip_id,
+ train_type => $trip->type,
+ train_line => $trip->line,
+ train_no => $trip->number,
+ origin => ( $trip->route )[0]->full_name,
+ destination => ( $trip->route )[-1]->full_name,
+ operators => [ $trip->operator ],
+ linetype => lc( $trip->product ) =~ tr{a-z}{}cdr,
+ route_pre_diff => [],
+ route_post_diff => [],
+ moreinfo => [],
+ replaced_by => [],
+ replacement_for => [],
+ };
+
+ if ( $res->{linetype} =~ m{strab|stra.?enbahn} ) {
+ $res->{linetype} = 'tram';
+ }
+ elsif ( $res->{linetype} =~ m{bus} ) {
+ $res->{linetype} = 'bus';
+ }
+
+ my $station_is_past = 1;
+ for my $stop ( $trip->route ) {
+
+ push(
+ @{ $res->{route_post_diff} },
+ {
+ name => $stop->full_name,
+ id => $stop->id_code,
+ sched_arr => $stop->sched_arr,
+ sched_dep => $stop->sched_dep,
+ rt_arr => $stop->rt_arr,
+ rt_dep => $stop->rt_dep,
+ arr_delay => $stop->arr_delay,
+ dep_delay => $stop->dep_delay,
+ platform => $stop->platform,
+ }
+ );
+ if (
+ $station_is_past
+ and $now->epoch < (
+ $res->{route_post_diff}[-1]{rt_arr}
+ // $res->{route_post_diff}[-1]{rt_dep}
+ // $res->{route_post_diff}[-1]{sched_arr}
+ // $res->{route_post_diff}[-1]{sched_dep} // $now
+ )->epoch
+ )
+ {
+ $station_is_past = 0;
+ }
+ $res->{route_post_diff}[-1]{isPast} = $station_is_past;
+ }
+
+ if ( my $req_id = $self->param('highlight') ) {
+ my $split;
+ for my $i ( 0 .. $#{ $res->{route_post_diff} } ) {
+ if ( $res->{route_post_diff}[$i]{id} eq $req_id ) {
+ $split = $i;
+ last;
+ }
+ }
+ if ( defined $split ) {
+ $self->stash(
+ station_name => $res->{route_post_diff}[$split]{name} );
+ for my $i ( 0 .. $split - 1 ) {
+ push(
+ @{ $res->{route_pre_diff} },
+ shift( @{ $res->{route_post_diff} } )
+ );
+ }
+ my $station_info = shift( @{ $res->{route_post_diff} } );
+ $res->{eva} = $station_info->{eva};
+ if ( $station_info->{sched_arr} ) {
+ $res->{sched_arrival}
+ = $station_info->{sched_arr}->strftime('%H:%M');
+ }
+ if ( $station_info->{rt_arr} ) {
+ $res->{arrival}
+ = $station_info->{rt_arr}->strftime('%H:%M');
+ }
+ if ( $station_info->{sched_dep} ) {
+ $res->{sched_departure}
+ = $station_info->{sched_dep}->strftime('%H:%M');
+ }
+ if ( $station_info->{rt_dep} ) {
+ $res->{departure}
+ = $station_info->{rt_dep}->strftime('%H:%M');
+ }
+ $res->{arrival_is_cancelled}
+ = $station_info->{arr_cancelled};
+ $res->{departure_is_cancelled}
+ = $station_info->{dep_cancelled};
+ $res->{is_cancelled} = $res->{arrival_is_cancelled}
+ || $res->{arrival_is_cancelled};
+ $res->{tz_offset} = $station_info->{tz_offset};
+ $res->{local_dt_da} = $station_info->{local_dt_da};
+ $res->{local_sched_arr} = $station_info->{local_sched_arr};
+ $res->{local_sched_dep} = $station_info->{local_sched_dep};
+ $res->{is_annotated} = $station_info->{is_annotated};
+ $res->{prod_name} = $station_info->{prod_name};
+ $res->{direction} = $station_info->{direction};
+ $res->{operator} = $station_info->{operator};
+ $res->{platform} = $station_info->{platform};
+ $res->{scheduled_platform}
+ = $station_info->{sched_platform};
+ }
+ }
+
+ $self->respond_to(
+ json => {
+ json => {
+ journey => $trip,
+ },
+ },
+ any => {
+ template => $self->param('ajax')
+ ? '_train_details'
+ : 'train_details',
+ description => sprintf(
+ '%s %s%s%s nach %s',
+ $res->{train_type},
+ $res->{train_line} // $res->{train_no},
+ $res->{origin} ? ' von ' : q{},
+ $res->{origin} // q{},
+ $res->{destination} // 'unbekannt'
+ ),
+ departure => $res,
+ linetype => $res->{linetype},
+ dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ },
+ );
+ }
+ )->catch(
+ sub {
+ my ($e) = @_;
+ $self->respond_to(
+ json => {
+ json => {
+ error => $e,
+ },
+ status => 500,
+ },
+ any => {
+ template => 'exception',
+ message => $e,
+ exception => undef,
+ snapshot => {},
+ status => 500,
+ },
+ );
+ }
+ )->wait;
+}
+
+# /z/:train
+sub train_details {
+ my ($self) = @_;
+ my $train = $self->stash('train');
+ my $dbris = $self->param('dbris');
+ my $efa = $self->param('efa');
+ my $hafas = $self->param('hafas');
+
+ # TODO error handling
+
+ if ( $self->param('ajax') ) {
+ delete $self->stash->{layout};
+ }
+
+ $self->stash( departures => [] );
+ $self->stash( title => 'DBF' );
+
+ if ($dbris) {
+ return $self->train_details_dbris;
+ }
+ if ($efa) {
+ return $self->train_details_efa;
+ }
+
+ my $res = {
+ train_type => undef,
+ train_line => undef,
+ train_no => undef,
+ route_pre_diff => [],
+ route_post_diff => [],
+ moreinfo => [],
+ replaced_by => [],
+ replacement_for => [],
+ };
+
+ my %opt;
+
+ if ( $train =~ m{[|]} ) {
+ $opt{trip_id} = $train;
+ }
+ else {
+ my ( $train_type, $train_no ) = ( $train =~ m{ ^ (\S+) \s+ (.*) $ }x );
+ $res->{train_type} = $train_type;
+ $res->{train_no} = $train_no;
+ $self->stash( title => "${train_type} ${train_no}" );
+ $opt{train_type} = $train_type;
+ $opt{train_no} = $train_no;
+ }
+
+ my $service = 'DB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $opt{service} = $hafas;
+ }
+
+ #if ( $self->languages =~ m{^en} ) {
+ # $opt{language} = 'en';
+ #}
+
+ if ( my $date = $self->param('date') ) {
+ if ( $date
+ =~ m{ ^ (?<day> \d{1,2} ) [.] (?<month> \d{1,2} ) [.] (?<year> \d{4})? $ }x
+ )
+ {
+ $opt{datetime} = DateTime->now( time_zone => 'Europe/Berlin' );
+ $opt{datetime}->set(
+ day => $+{day},
+ month => $+{month}
+ );
+ if ( $+{year} ) {
+ $opt{datetime}->set( year => $+{year} );
+ }
+ }
+ }
+
+ $self->stash( hide_opts => 1 );
+ $self->render_later;
+
+ my $linetype = 'bahn';
+
+ $self->hafas->get_route_p(%opt)->then(
+ sub {
+ my ( $route, $journey, $hafas_obj ) = @_;
+
+ $res->{trip_id} = $journey->id;
+ $res->{date} = $route->[0]{sched_dep} // $route->[0]{dep};
+
+ my $product = $journey->product;
+
+ if ( my $req_name = $self->param('highlight') ) {
+ if ( my $p = $journey->product_at($req_name) ) {
+ $product = $p;
+ }
+ }
+
+ my $train_type = $res->{train_type} = $product->type // q{};
+ my $train_no = $res->{train_no} = $product->number // q{};
+ $res->{train_line} = $product->line_no // q{};
+ $self->stash( title => $train_type . ' '
+ . ( $train_no || $res->{train_line} ) );
+
+ if ( not defined $product->class ) {
+ $linetype = 'ext';
+ }
+ else {
+ my $prod
+ = $self->class_to_product($hafas_obj)->{ $product->class }
+ // q{};
+ if ( $prod =~ m{ ^ ice? | inter-?cit }ix ) {
+ $linetype = 'fern';
+ }
+ elsif ( $prod =~ m{ s-bahn | urban | rapid }ix ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $prod =~ m{ bus }ix ) {
+ $linetype = 'bus';
+ }
+ elsif ( $prod =~ m{ metro | u-bahn | subway }ix ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $prod =~ m{ tram }ix ) {
+ $linetype = 'tram';
+ }
+ }
+
+ $res->{origin} = $journey->route_start;
+ $res->{destination} = $journey->route_end;
+ $res->{operators} = [ $journey->operators ];
+
+ $res->{route_post_diff} = $route;
+
+ if ( my $req_name = $self->param('highlight') ) {
+ my $split;
+ for my $i ( 0 .. $#{ $res->{route_post_diff} } ) {
+ if ( $res->{route_post_diff}[$i]{name} eq $req_name ) {
+ $split = $i;
+ last;
+ }
+ }
+ if ( defined $split ) {
+ $self->stash( station_name => $req_name );
+ for my $i ( 0 .. $split - 1 ) {
+ push(
+ @{ $res->{route_pre_diff} },
+ shift( @{ $res->{route_post_diff} } )
+ );
+ }
+ my $station_info = shift( @{ $res->{route_post_diff} } );
+ $res->{eva} = $station_info->{eva};
+ if ( $station_info->{sched_arr} ) {
+ $res->{sched_arrival}
+ = $station_info->{sched_arr}->strftime('%H:%M');
+ }
+ if ( $station_info->{rt_arr} ) {
+ $res->{arrival}
+ = $station_info->{rt_arr}->strftime('%H:%M');
+ }
+ if ( $station_info->{sched_dep} ) {
+ $res->{sched_departure}
+ = $station_info->{sched_dep}->strftime('%H:%M');
+ }
+ if ( $station_info->{rt_dep} ) {
+ $res->{departure}
+ = $station_info->{rt_dep}->strftime('%H:%M');
+ }
+ $res->{arrival_is_cancelled}
+ = $station_info->{arr_cancelled};
+ $res->{departure_is_cancelled}
+ = $station_info->{dep_cancelled};
+ $res->{is_cancelled} = $res->{arrival_is_cancelled}
+ || $res->{arrival_is_cancelled};
+ $res->{tz_offset} = $station_info->{tz_offset};
+ $res->{local_dt_da} = $station_info->{local_dt_da};
+ $res->{local_sched_arr} = $station_info->{local_sched_arr};
+ $res->{local_sched_dep} = $station_info->{local_sched_dep};
+ $res->{is_annotated} = $station_info->{is_annotated};
+ $res->{prod_name} = $station_info->{prod_name};
+ $res->{direction} = $station_info->{direction};
+ $res->{operator} = $station_info->{operator};
+ $res->{platform} = $station_info->{platform};
+ $res->{scheduled_platform}
+ = $station_info->{sched_platform};
+ }
+ }
+
+ my @him_messages;
+ my @him_details;
+ for my $message ( $journey->messages ) {
+ if ( $message->code ) {
+ push( @him_details,
+ [ $message->short // q{}, { text => $message->text } ]
+ );
+ }
+ else {
+ push( @him_messages,
+ [ $message->short // q{}, { text => $message->text } ]
+ );
+ }
+ }
+ for my $m (@him_messages) {
+ if ( $m->[0] =~ s{: Information.}{:} ) {
+ $m->[1]{icon} = 'info_outline';
+ }
+ elsif ( $m->[0] =~ s{: Störung.}{: } ) {
+ $m->[1]{icon} = 'warning';
+ }
+ elsif ( $m->[0] =~ s{: Bauarbeiten.}{: } ) {
+ $m->[1]{icon} = 'build';
+ }
+ }
+ if (@him_messages) {
+ $res->{moreinfo} = [@him_messages];
+ }
+ if (@him_details) {
+ $res->{details} = [@him_details];
+ }
+
+ $self->respond_to(
+ json => {
+ json => {
+ journey => $journey,
+ },
+ },
+ any => {
+ template => $self->param('ajax')
+ ? '_train_details'
+ : 'train_details',
+ description => sprintf(
+ '%s %s%s%s nach %s',
+ $res->{train_type},
+ $res->{train_line} // $res->{train_no},
+ $res->{origin} ? ' von ' : q{},
+ $res->{origin} // q{},
+ $res->{destination} // 'unbekannt'
+ ),
+ departure => $res,
+ linetype => $linetype,
+ dt_now => DateTime->now( time_zone => 'Europe/Berlin' ),
+ },
+ );
+ }
+ )->catch(
+ sub {
+ my ($e) = @_;
+ if ($e) {
+ $self->respond_to(
+ json => {
+ json => {
+ error => $e,
+ },
+ status => 500,
+ },
+ any => {
+ template => 'exception',
+ message => $e,
+ exception => undef,
+ snapshot => {},
+ status => 500,
+ },
+ );
+ }
+ else {
+ $self->render( 'not_found', status => 404 );
+ }
+ }
+ )->wait;
+}
+
+sub render_board_dbris {
+ my ( $self, $station_id, $dbris ) = @_;
+ my $template = $self->param('mode') // 'app';
+ my $hide_low_delay = $self->param('hidelowdelay') // 0;
+ my $hide_opts = $self->param('hide_opts') // 0;
+ my $show_realtime = $self->param('rt') // $self->param('show_realtime')
+ // 1;
+
+ my $station_name;
+ if ( $station_id =~ m{ [@] O = (?<name> [^@]+) [@] }x ) {
+ $station_name = $+{name};
+ }
+
+ my @departures;
+
+ if ( $self->param('ajax') ) {
+ delete $self->stash->{layout};
+ }
+
+ my @results = $self->filter_results( $dbris->results );
+
+ @results = map { $_->[1] } sort { $a->[0] <=> $b->[0] }
+ map { [ $_->dep, $_ ] } @results;
+
+ for my $result (@results) {
+ my $time;
+
+ if ( $template eq 'json' ) {
+ push( @departures, $result );
+ next;
+ }
+
+ if ( $show_realtime and $result->rt_dep ) {
+ $time = $result->rt_dep->strftime('%H:%M');
+ }
+ else {
+ $time = $result->sched_dep->strftime('%H:%M');
+ }
+
+ my $linetype = $result->line;
+ if ( $linetype =~ m{ STR }x ) {
+ $linetype = 'tram';
+ }
+ elsif ( $linetype =~ m{ ^ S }x ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $linetype =~ m{ U }x ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $linetype =~ m{ Bus }x ) {
+ $linetype = 'bus';
+ }
+ elsif ( $linetype =~ m{ ^ [EI]CE? }x ) {
+ $linetype = 'fern';
+ }
+ elsif ( $linetype =~ m{ EST | FLX }x ) {
+ $linetype = 'ext';
+ }
+ else {
+ $linetype = 'bahn';
+ }
+
+ my $delay = $result->delay;
+
+ push(
+ @departures,
+ {
+ time => $time,
+ sched_departure => $result->sched_dep->strftime('%H:%M'),
+ departure => $result->rt_dep
+ ? $result->rt_dep->strftime('%H:%M')
+ : undef,
+ train => $result->train_mid,
+ train_type => q{},
+ train_line => $result->line,
+ train_no => $result->maybe_train_no,
+ journey_id => $result->id,
+ via => [ $result->via ],
+ origin => q{},
+ destination => $result->destination,
+ platform => $result->rt_platform // $result->platform,
+ scheduled_platform => $result->platform,
+ is_cancelled => $result->is_cancelled,
+ linetype => $linetype,
+ delay => $delay,
+ is_bit_delayed =>
+ ( $delay and $delay > 0 and $delay < 5 ? 1 : 0 ),
+ is_delayed => ( $delay and $delay >= 5 ? 1 : 0 ),
+ has_realtime => defined $delay ? 1 : 0,
+ station => $result->stop_eva,
+ replaced_by => [],
+ replacement_for => [],
+ route_pre => [],
+ route_post => [ $result->via ],
+ wr_dt => undef,
+ }
+ );
+ }
+
+ if ( $template eq 'json' ) {
+ $self->res->headers->access_control_allow_origin(q{*});
+ my $json = {
+ departures => \@departures,
+ };
+ $self->render(
+ json => $json,
+ );
+ }
+ else {
+ $self->render(
+ $template,
+ description => "Abfahrtstafel $station_name",
+ departures => \@departures,
+ station => $station_name,
+ version => $self->config->{version},
+ title => $station_name,
+ refresh_interval => $template eq 'app' ? 0 : 120,
+ hide_opts => $hide_opts,
+ hide_footer => $hide_opts,
+ hide_low_delay => $hide_low_delay,
+ show_realtime => $show_realtime,
+ load_marquee => (
+ $template eq 'single'
+ or $template eq 'multi'
+ ),
+ force_mobile => ( $template eq 'app' ),
+ );
+ }
+}
+
+sub render_board_efa {
+ my ( $self, $station_name, $efa ) = @_;
+ my $template = $self->param('mode') // 'app';
+ my $hide_low_delay = $self->param('hidelowdelay') // 0;
+ my $hide_opts = $self->param('hide_opts') // 0;
+ my $show_realtime = $self->param('rt') // $self->param('show_realtime')
+ // 1;
+
+ my @departures;
+
+ if ( $self->param('ajax') ) {
+ delete $self->stash->{layout};
+ }
+
+ my @results = $self->filter_results( $efa->results );
+
+ for my $result (@results) {
+ my $time;
+
+ if ( $template eq 'json' ) {
+ push( @departures, $result );
+ next;
+ }
+
+ if ( $show_realtime and $result->rt_datetime ) {
+ $time = $result->rt_datetime->strftime('%H:%M');
+ }
+ else {
+ $time = $result->sched_datetime->strftime('%H:%M');
+ }
+
+ my $linetype = $result->mot_name // 'bahn';
+ if ( $linetype =~ m{ s-bahn | urban | rapid }ix ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $linetype =~ m{ metro | u-bahn | subway }ix ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $linetype =~ m{ bus }ix ) {
+ $linetype = 'bus';
+ }
+ elsif ( $linetype =~ m{ tram }ix ) {
+ $linetype = 'tram';
+ }
+ elsif ( $linetype =~ m{ ^ ice? | inter-?cit }ix ) {
+ $linetype = 'fern';
+ }
+ elsif ( $linetype eq 'sonstige' ) {
+ $linetype = 'ext';
+ }
+
+ my $delay = $result->delay;
+
+ push(
+ @departures,
+ {
+ time => $time,
+ sched_departure => $result->sched_datetime->strftime('%H:%M'),
+ departure => $result->rt_datetime
+ ? $result->rt_datetime->strftime('%H:%M')
+ : undef,
+ train => $result->line,
+ train_type => q{},
+ train_line => $result->line,
+ train_no => $result->train_no,
+ journey_id => $result->id,
+ via => [ map { $_->name } $result->route_interesting ],
+ origin => $result->origin,
+ destination => $result->destination,
+ platform => $result->platform,
+ is_cancelled => $result->is_cancelled,
+ linetype => $linetype,
+ delay => $delay,
+ is_bit_delayed =>
+ ( $delay and $delay > 0 and $delay < 5 ? 1 : 0 ),
+ is_delayed => ( $delay and $delay >= 5 ? 1 : 0 ),
+ has_realtime => defined $delay ? 1 : 0,
+ occupancy => $result->occupancy,
+ station => $efa->stop->id_code,
+ replaced_by => [],
+ replacement_for => [],
+ route_pre => [ map { $_->full_name } $result->route_pre ],
+ route_post => [ map { $_->full_name } $result->route_post ],
+ wr_dt => undef,
+ }
+ );
+ }
+
+ if ( $template eq 'json' ) {
+ $self->res->headers->access_control_allow_origin(q{*});
+ my $json = {
+ departures => \@departures,
+ };
+ $self->render(
+ json => $json,
+ );
+ }
+ else {
+ $self->render(
+ $template,
+ description => "Abfahrtstafel $station_name",
+ departures => \@departures,
+ station => $efa->stop->name,
+ version => $self->config->{version},
+ title => $efa->stop->name // $station_name,
+ refresh_interval => $template eq 'app' ? 0 : 120,
+ hide_opts => $hide_opts,
+ hide_footer => $hide_opts,
+ hide_low_delay => $hide_low_delay,
+ show_realtime => $show_realtime,
+ load_marquee => (
+ $template eq 'single'
+ or $template eq 'multi'
+ ),
+ force_mobile => ( $template eq 'app' ),
+ );
+ }
+}
+
+# For HAFAS and IRIS departure elements
+sub render_board_hafas {
+ my ( $self, $data ) = @_;
+
+ my @results = @{ $data->{results} };
+ my @departures;
+
+ my @platforms = split( /,/, $self->param('platforms') // q{} );
+ my $template = $self->param('mode') // 'app';
+ my $hide_low_delay = $self->param('hidelowdelay') // 0;
+ my $hide_opts = $self->param('hide_opts') // 0;
+ my $show_realtime = $self->param('rt') // $self->param('show_realtime')
+ // 1;
+ my $show_details = $self->param('detailed') // 0;
+ my $admode = $self->param('admode') // 'deparr';
+ my $apiver = $self->param('version') // 0;
+ my $callback = $self->param('callback');
+ my $via = $self->param('via');
+ my $hafas = $self->param('hafas');
+ my $hafas_obj = $data->{hafas};
+
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ if ( $self->param('ajax') ) {
+ delete $self->stash->{layout};
+ }
+
+ if ( $template eq 'single' ) {
+ if ( not @platforms ) {
+ for my $result (@results) {
+ my $num_part
+ = $self->numeric_platform_part( $result->platform );
+ if (
+ not( List::MoreUtils::any { $num_part eq $_ } @platforms ) )
+ {
+ push( @platforms, $num_part );
+ }
+ }
+ @platforms = sort { $a <=> $b } @platforms;
+ }
+ my %pcnt;
+ @results
+ = grep { $pcnt{ $self->numeric_platform_part( $_->platform ) }++ < 1 }
+ @results;
+ @results = map { $_->[1] }
+ sort { $a->[0] <=> $b->[0] }
+ map { [ $self->numeric_platform_part( $_->platform ), $_ ] } @results;
+ }
+
+ if ($show_realtime) {
+ if ($hafas) {
+ @results = sort { $a->datetime <=> $b->datetime } @results;
+ }
+ elsif ( $admode eq 'arr' ) {
+ @results = map { $_->[1] }
+ sort { $a->[0] <=> $b->[0] }
+ map {
+ [
+ (
+ $_->sched_arrival ? $_->arrival_is_cancelled
+ : $_->is_cancelled
+ ) ? ( $_->sched_arrival // $_->sched_departure )
+ : ( $_->arrival // $_->departure ),
+ $_
+ ]
+ } @results;
+ }
+ else {
+ @results = map { $_->[1] }
+ sort { $a->[0] <=> $b->[0] }
+ map {
+ [
+ (
+ $_->sched_departure ? $_->departure_is_cancelled
+ : $_->is_cancelled
+ ) ? ( $_->sched_departure // $_->sched_arrival )
+ : ( $_->departure // $_->arrival ),
+ $_
+ ]
+ } @results;
+ }
+ }
+
+ my $class_to_product
+ = $hafas_obj ? $self->class_to_product($hafas_obj) : {};
+
+ @results = $self->filter_results(@results);
+
+ for my $result (@results) {
+ my $platform = ( split( qr{ }, $result->platform // '' ) )[0];
+ my $delay = $result->delay;
+ if ( $admode eq 'arr' and not $hafas and not $result->arrival ) {
+ next;
+ }
+ if ( $admode eq 'dep'
+ and not $hafas
+ and not $result->departure )
+ {
+ next;
+ }
+ my ( $info, $moreinfo );
+ if ( $result->can('replacement_for') ) {
+ ( $info, $moreinfo )
+ = $self->format_iris_result_info( $template, $result );
+ }
+
+ my $time
+ = $result->can('time')
+ ? $result->time
+ : $result->sched_datetime->strftime('%H:%M');
+ my $linetype = 'bahn';
+
+ if ( $result->can('classes') ) {
+ my @classes = $result->classes;
+ if ( @classes == 0 ) {
+ $linetype = 'ext';
+ }
+ elsif ( grep { $_ eq 'S' } @classes ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( grep { $_ eq 'F' } @classes ) {
+ $linetype = 'fern';
+ }
+ }
+ elsif ( $result->can('class') ) {
+ my $prod = $class_to_product->{ $result->class } // q{};
+ if ( $prod =~ m{ ^ ice? | inter-?cit }ix ) {
+ $linetype = 'fern';
+ }
+ elsif ( $prod =~ m{ s-bahn | urban | rapid }ix ) {
+ $linetype = 'sbahn';
+ }
+ elsif ( $prod =~ m{ bus }ix ) {
+ $linetype = 'bus';
+ }
+ elsif ( $prod =~ m{ metro | u-bahn | subway }ix ) {
+ $linetype = 'ubahn';
+ }
+ elsif ( $prod =~ m{ tram }ix ) {
+ $linetype = 'tram';
+ }
+ }
+
+ # ->time defaults to dep, so we only need to overwrite $time
+ # if we want arrival times
+ if ( $admode eq 'arr' and not $hafas ) {
+ $time = $result->sched_arrival->strftime('%H:%M');
+ }
+
+ if ($show_realtime) {
+ if ($hafas) {
+ $time = $result->datetime->strftime('%H:%M');
+ }
+ elsif ( ( $admode eq 'arr' and $result->arrival )
+ or not $result->departure )
+ {
+ $time = $result->arrival->strftime('%H:%M');
+ }
+ else {
+ $time = $result->departure->strftime('%H:%M');
+ }
+ }
+
+ if ($hide_low_delay) {
+ if ($info) {
+ $info =~ s{ (?: ca [.] \s* )? [+] [ 1 2 3 4 ] $ }{}x;
+ }
+ }
+ if ($info) {
+ $info =~ s{ (?: ca [.] \s* )? [+] (\d+) }{Verspätung ca $1 Min.}x;
+ }
+
+ if ( $template eq 'json' ) {
+ my @json_route;
+ if ( $result->can('sched_route') ) {
+ @json_route = $self->json_route_diff( [ $result->route ],
+ [ $result->sched_route ] );
+ }
+ else {
+ @json_route = map { $_->TO_JSON } $result->route;
+ }
+
+ if ( $apiver eq '1' or $apiver eq '2' ) {
+
+ # no longer supported
+ $self->handle_no_results_json(
+ undef,
+ {
+ errstr =>
+ "JSON API version=${apiver} is no longer supported"
+ },
+ $Travel::Status::DE::IRIS::VERSION
+ );
+ return;
+ }
+ elsif ( $apiver eq 'raw' ) {
+ push( @departures, $result );
+ }
+ else { # apiver == 3
+ if ( $result->isa('Travel::Status::DE::IRIS::Result') ) {
+ my ( $delay_arr, $delay_dep, $sched_arr, $sched_dep );
+ if ( $result->arrival ) {
+ $delay_arr = $result->arrival->subtract_datetime(
+ $result->sched_arrival )->in_units('minutes');
+ }
+ if ( $result->departure ) {
+ $delay_dep = $result->departure->subtract_datetime(
+ $result->sched_departure )->in_units('minutes');
+ }
+ if ( $result->sched_arrival ) {
+ $sched_arr = $result->sched_arrival->strftime('%H:%M');
+ }
+ if ( $result->sched_departure ) {
+ $sched_dep
+ = $result->sched_departure->strftime('%H:%M');
+ }
+ push(
+ @departures,
+ {
+ delayArrival => $delay_arr,
+ delayDeparture => $delay_dep,
+ destination => $result->destination,
+ isCancelled => $result->is_cancelled,
+ messages => {
+ delay => [
+ map {
+ {
+ timestamp => $_->[0],
+ text => $_->[1]
+ }
+ } $result->delay_messages
+ ],
+ qos => [
+ map {
+ {
+ timestamp => $_->[0],
+ text => $_->[1]
+ }
+ } $result->qos_messages
+ ],
+ },
+ missingRealtime => (
+ (
+ not $result->has_realtime
+ and $result->start < $now
+ ) ? \1 : \0
+ ),
+ platform => $result->platform,
+ route => \@json_route,
+ scheduledPlatform => $result->sched_platform,
+ scheduledArrival => $sched_arr,
+ scheduledDeparture => $sched_dep,
+ train => $result->train,
+ trainClasses => [ $result->classes ],
+ trainNumber => $result->train_no,
+ via => [ $result->route_interesting(3) ],
+ }
+ );
+ }
+ else {
+ push(
+ @departures,
+ {
+ delay => $result->delay,
+ direction => $result->direction,
+ destination => $result->destination,
+ isCancelled => $result->is_cancelled,
+ messages => [ $result->messages ],
+ platform => $result->platform,
+ route => \@json_route,
+ scheduledPlatform => $result->sched_platform,
+ scheduledTime => $result->sched_datetime->epoch,
+ time => $result->datetime->epoch,
+ train => $result->line,
+ trainNumber => $result->number,
+ via => [ $result->route_interesting(3) ],
+ }
+ );
+ }
+ }
+ }
+ elsif ( $template eq 'text' ) {
+ push(
+ @departures,
+ [
+ sprintf( '%5s %s%s',
+ $result->is_cancelled ? '--:--' : $time,
+ ( $delay and $delay > 0 ) ? q{+} : q{},
+ $delay || q{} ),
+ $result->train,
+ $result->destination,
+ $platform // q{ }
+ ]
+ );
+ }
+ else {
+ if ( $result->can('replacement_for') ) {
+ push(
+ @departures,
+ {
+ time => $time,
+ sched_arrival => $result->sched_arrival
+ ? $result->sched_arrival->strftime('%H:%M')
+ : undef,
+ sched_departure => $result->sched_departure
+ ? $result->sched_departure->strftime('%H:%M')
+ : undef,
+ arrival => $result->arrival
+ ? $result->arrival->strftime('%H:%M')
+ : undef,
+ departure => $result->departure
+ ? $result->departure->strftime('%H:%M')
+ : undef,
+ train => $result->train,
+ train_type => $result->type // '',
+ train_line => $result->line_no,
+ train_no => $result->train_no,
+ via => [ $result->route_interesting(3) ],
+ destination => $result->destination,
+ origin => $result->origin,
+ platform => $result->platform,
+ scheduled_platform => $result->sched_platform,
+ info => $info,
+ is_cancelled => $result->is_cancelled,
+ departure_is_cancelled =>
+ $result->departure_is_cancelled,
+ arrival_is_cancelled => $result->arrival_is_cancelled,
+ linetype => $linetype,
+ messages => {
+ delay => [
+ map {
+ {
+ timestamp => $_->[0],
+ text => $_->[1]
+ }
+ } $result->delay_messages
+ ],
+ qos => [
+ map {
+ {
+ timestamp => $_->[0],
+ text => $_->[1]
+ }
+ } $result->qos_messages
+ ],
+ },
+ station => $result->station,
+ moreinfo => $moreinfo,
+ delay => $delay,
+ is_bit_delayed =>
+ ( $delay and $delay > 0 and $delay < 5 ? 1 : 0 ),
+ is_delayed => ( $delay and $delay >= 5 ? 1 : 0 ),
+ arrival_delay => $result->arrival_delay,
+ departure_delay => $result->departure_delay,
+ has_realtime => $result->has_realtime,
+ missing_realtime => (
+ not $result->has_realtime
+ and $result->start < $now ? 1 : 0
+ ),
+ route_pre => [ $result->route_pre ],
+ route_post => [ $result->route_post ],
+ additional_stops => [ $result->additional_stops ],
+ canceled_stops => [ $result->canceled_stops ],
+ replaced_by => [
+ map { $_->type . q{ } . $_->train_no }
+ $result->replaced_by
+ ],
+ replacement_for => [
+ map { $_->type . q{ } . $_->train_no }
+ $result->replacement_for
+ ],
+ wr_dt => $result->sched_departure,
+ eva => $result->station_uic,
+ }
+ );
+ }
+ else {
+ my $city = q{};
+ if ( $result->station =~ m{ , ([^,]+) $ }x ) {
+ $city = $1;
+ }
+ push(
+ @departures,
+ {
+ time => $time,
+ sched_departure =>
+ ( $result->sched_datetime and $admode ne 'arr' )
+ ? $result->sched_datetime->strftime('%H:%M')
+ : undef,
+ departure =>
+ ( $result->rt_datetime and $admode ne 'arr' )
+ ? $result->rt_datetime->strftime('%H:%M')
+ : undef,
+ train => $result->name,
+ train_type => q{},
+ train_line => $result->line,
+ train_no => $result->number,
+ journey_id => $result->id,
+ via => [
+ map { $_->loc->name =~ s{,\Q$city\E}{}r }
+ $result->route_interesting(3)
+ ],
+ destination => $result->route_end =~ s{,\Q$city\E}{}r,
+ origin => $result->route_end =~ s{,\Q$city\E}{}r,
+ platform => $result->platform,
+ scheduled_platform => $result->sched_platform,
+ load => $result->load // {},
+ info => $info,
+ is_cancelled => $result->is_cancelled,
+ linetype => $linetype,
+ station => $result->station,
+ moreinfo => $moreinfo,
+ delay => $delay,
+ is_bit_delayed =>
+ ( $delay and $delay > 0 and $delay < 5 ? 1 : 0 ),
+ is_delayed => ( $delay and $delay >= 5 ? 1 : 0 ),
+ has_realtime => defined $delay ? 1 : 0,
+ replaced_by => [],
+ replacement_for => [],
+ route_pre => $admode eq 'arr'
+ ? [ map { $_->loc->name } $result->route ]
+ : [],
+ route_post => $admode eq 'arr' ? []
+ : [ map { $_->loc->name } $result->route ],
+ wr_dt => $result->sched_datetime,
+ eva => $result->station_uic,
+ }
+ );
+ }
+ if ( $self->param('train') ) {
+ $self->render_train( $result, $departures[-1],
+ $data->{station_name} // $self->stash('station') );
+ return;
+ }
+ }
+ }
+
+ if ( $template eq 'json' ) {
+ $self->res->headers->access_control_allow_origin(q{*});
+ my $json = {
+ departures => \@departures,
+ };
+ if ($callback) {
+ $json = $self->render_to_string( json => $json );
+ $self->render(
+ data => "$callback($json);",
+ format => 'json'
+ );
+ }
+ else {
+ $self->render(
+ json => $json,
+ );
+ }
+ }
+ elsif ( $template eq 'text' ) {
+ my @line_length;
+ for my $i ( 0 .. $#{ $departures[0] } ) {
+ $line_length[$i] = max map { length( $_->[$i] ) } @departures;
+ }
+ my $output = q{};
+ for my $departure (@departures) {
+ $output .= sprintf(
+ join( q{ }, ( map { "%-${_}s" } @line_length ) ) . "\n",
+ @{$departure}[ 0 .. $#{$departure} ]
+ );
+ }
+ $self->render(
+ text => $output,
+ format => 'text',
+ );
+ }
+ else {
+ my $station_name = $data->{station_name} // $self->stash('station');
+ my ( $api_link, $api_text, $api_icon );
+ my $params = $self->req->params->clone;
+ if ( not $hafas ) {
+ if ( $data->{station_eva} >= 8100000
+ and $data->{station_eva} < 8200000 )
+ {
+ $params->param( hafas => 'ÖBB' );
+ }
+ elsif ( $data->{station_eva} >= 8500000
+ and $data->{station_eva} < 8600000 )
+ {
+ $params->param( hafas => 'BLS' );
+ }
+ if ( $params->param('hafas') ) {
+ $api_link
+ = '/' . $data->{station_eva} . '?' . $params->to_string;
+ $api_text = 'Auf Nahverkehr wechseln';
+ $api_icon = 'train';
+ }
+ }
+ $self->render(
+ $template,
+ description => 'Abfahrtstafel '
+ . ( $via ? "$station_name via $via" : $station_name ),
+ api_link => $api_link,
+ api_text => $api_text,
+ api_icon => $api_icon,
+ departures => \@departures,
+ station => $station_name,
+ version => $self->config->{version},
+ title => $via ? "$station_name → $via" : $station_name,
+ refresh_interval => $template eq 'app' ? 0 : 120,
+ hide_opts => $hide_opts,
+ hide_footer => $hide_opts,
+ hide_low_delay => $hide_low_delay,
+ show_realtime => $show_realtime,
+ load_marquee => (
+ $template eq 'single'
+ or $template eq 'multi'
+ ),
+ force_mobile => ( $template eq 'app' ),
+ nav_link =>
+ $self->url_for( 'station', station => $station_name )->query(
+ {
+ detailed => $self->param('detailed'),
+ hafas => $self->param('hafas')
+ }
+ ),
+ );
+ }
+ return;
+}
+
+sub stations_by_coordinates {
+ my $self = shift;
+
+ my $lon = $self->param('lon');
+ my $lat = $self->param('lat');
+ my $efa_service = $self->param('efa');
+ my $hafas = $self->param('hafas');
+
+ if ( not $lon or not $lat ) {
+ $self->render( json => { error => 'Invalid lon/lat received' } );
+ return;
+ }
+
+ my $service = 'ÖBB';
+ if ( $hafas
+ and $hafas ne '1'
+ and Travel::Status::DE::HAFAS::get_service($hafas) )
+ {
+ $service = $hafas;
+ }
+
+ $self->render_later;
+
+ if ($efa_service) {
+ Travel::Status::DE::EFA->new_p(
+ promise => 'Mojo::Promise',
+ user_agent => $self->ua,
+ service => $efa_service,
+ coord => {
+ lat => $lat,
+ lon => $lon
+ }
+ )->then(
+ sub {
+ my ($efa) = @_;
+ my @efa = map {
+ {
+ name => $_->full_name,
+ eva => $_->id =~ s{:}{%3A}gr,
+ distance => $_->distance_m / 1000,
+ efa => $efa_service,
+ }
+ } $efa->results;
+ $self->render(
+ json => {
+ candidates => [@efa],
+ }
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ json => {
+ candidates => [],
+ warning => $err,
+ }
+ );
+ }
+ )->wait;
+ return;
+ }
+
+ my @iris = map {
+ {
+ ds100 => $_->[0][0],
+ name => $_->[0][1],
+ eva => $_->[0][2],
+ lon => $_->[0][3],
+ lat => $_->[0][4],
+ distance => $_->[1],
+ hafas => 0,
+ }
+ } Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
+ $lat, 10 );
+
+ @iris = uniq_by { $_->{name} } @iris;
+
+ Travel::Status::DE::HAFAS->new_p(
+ promise => 'Mojo::Promise',
+ user_agent => $service eq 'PKP' ? Mojo::UserAgent->new : $self->ua,
+ service => $service,
+ geoSearch => {
+ lat => $lat,
+ lon => $lon
+ }
+ )->then(
+ sub {
+ my ($hafas) = @_;
+ my @hafas = map {
+ {
+ name => $_->name,
+ eva => $_->eva,
+ distance => $_->distance_m / 1000,
+ hafas => $service,
+ }
+ } $hafas->results;
+ if ( @hafas > 10 ) {
+ @hafas = @hafas[ 0 .. 9 ];
+ }
+ my @results = map { $_->[0] }
+ sort { $a->[1] <=> $b->[1] }
+ map { [ $_, $_->{distance} ] } ( @iris, @hafas );
+ $self->render(
+ json => {
+ candidates => [@results],
+ }
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->render(
+ json => {
+ candidates => [@iris],
+ warning => $err,
+ }
+ );
+ }
+ )->wait;
+}
+
+sub backend_list {
+ my ($self) = @_;
+
+ my %place_map = (
+ AT => 'Österreich',
+ CH => 'Schweiz',
+ 'CH-BE' => 'Kanton Bern',
+ 'CH-GE' => 'Kanton Genf',
+ 'CH-LU' => 'Kanton Luzern',
+ 'CH-ZH' => 'Kanton Zürich',
+ DE => 'Deutschland',
+ 'DE-BB' => 'Brandenburg',
+ 'DE-BW' => 'Baden-Württemberg',
+ 'DE-BE' => 'Berlin',
+ 'DE-BY' => 'Bayern',
+ 'DE-HB' => 'Bremen',
+ 'DE-HE' => 'Hessen',
+ 'DE-MV' => 'Mecklenburg-Vorpommern',
+ 'DE-NI' => 'Niedersachsen',
+ 'DE-NW' => 'Nordrhein-Westfalen',
+ 'DE-RP' => 'Rheinland-Pfalz',
+ 'DE-SH' => 'Schleswig-Holstein',
+ 'DE-ST' => 'Sachsen-Anhalt',
+ 'DE-TH' => 'Thüringen',
+ DK => 'Dänemark',
+ 'GB-NIR' => 'Nordirland',
+ LI => 'Liechtenstein',
+ LU => 'Luxembourg',
+ IE => 'Irland',
+ 'US-CA' => 'California',
+ 'US-TX' => 'Texas',
+ );
+
+ my @backends = (
+ {
+ name => 'Deutsche Bahn',
+ type => 'IRIS-TTS',
+ }
+ );
+
+ for my $backend ( Travel::Status::DE::EFA::get_services() ) {
+ push(
+ @backends,
+ {
+ name => $backend->{name},
+ shortname => $backend->{shortname},
+ homepage => $backend->{homepage},
+ regions => [
+ map { $place_map{$_} // $_ }
+ @{ $backend->{coverage}{regions} }
+ ],
+ has_area => $backend->{coverage}{area} ? 1 : 0,
+ type => 'EFA',
+ efa => 1,
+ }
+ );
+ }
+
+ for my $backend ( Travel::Status::DE::HAFAS::get_services() ) {
+ if ( $backend->{shortname} eq 'DB' ) {
+
+ # HTTP 503 Service Temporarily Unavailable as of 2025-01-08 ~10:30 UTC
+ # (I bet it's actually Permanently Unavailable)
+ next;
+ }
+ if ( $backend->{shortname} eq 'VRN' ) {
+
+ # HTTP 403 Forbidden as of 2025-03-03
+ next;
+ }
+ push(
+ @backends,
+ {
+ name => $backend->{name},
+ shortname => $backend->{shortname},
+ homepage => $backend->{homepage},
+ regions => [
+ map { $place_map{$_} // $_ }
+ @{ $backend->{coverage}{regions} }
+ ],
+ has_area => $backend->{coverage}{area} ? 1 : 0,
+ type => 'HAFAS',
+ hafas => 1,
+ }
+ );
+ }
+
+ $self->render(
+ 'select_backend',
+ backends => \@backends,
+ hide_opts => 1,
+ hide_footer => 1
+ );
+}
+
+sub autocomplete {
+ my ($self) = @_;
+
+ $self->res->headers->cache_control('max-age=31536000, immutable');
+
+ my $output = '$(function(){const stations=';
+ $output
+ .= encode_json(
+ [ map { $_->[1] } Travel::Status::DE::IRIS::Stations::get_stations() ]
+ );
+ $output .= ";\n";
+ $output
+ .= "\$('input.station').autocomplete({delay:0,minLength:3,source:stations});});\n";
+
+ $self->render(
+ format => 'js',
+ data => $output
+ );
+}
+
+sub redirect_to_station {
+ my ($self) = @_;
+ my $input = $self->param('input');
+ my $params = $self->req->params;
+
+ $params->remove('input');
+
+ for my $param (qw(platforms mode admode via)) {
+ if (
+ not $params->param($param)
+ or ( exists $default{$param}
+ and $params->param($param) eq $default{$param} )
+ )
+ {
+ $params->remove($param);
+ }
+ }
+
+ if ( $input =~ m{ ^ [a-zA-Z]{1,5} \s+ \d+ }x ) {
+ if ( $input =~ s{ \s* @ \s* (?<date> [0-9.]+) $ }{}x ) {
+ $params->param( date => $+{date} );
+ }
+ elsif ( $input =~ s{ \s* [(] \s* (?<date> [0-9.]+) \s* [)] $ }{}x ) {
+ $params->param( date => $+{date} );
+ }
+ $params = $params->to_string;
+ $self->redirect_to("/z/${input}?${params}");
+ }
+ elsif ( $params->param('efa') ) {
+ $params->remove('hafas');
+ $params = $params->to_string;
+ $self->redirect_to("/${input}?${params}");
+ }
+ elsif ( $params->param('hafas') and $params->param('hafas') ne '1' ) {
+ $params->remove('efa');
+ $params = $params->to_string;
+ $self->redirect_to("/${input}?${params}");
+ }
+ else {
+ $params->remove('efa');
+ my @candidates
+ = Travel::Status::DE::IRIS::Stations::get_station($input);
+ if (
+ @candidates == 1
+ and ( $input eq $candidates[0][0]
+ or lc($input) eq lc( $candidates[0][1] )
+ or $input eq $candidates[0][2] )
+ )
+ {
+ $params->remove('hafas');
+ }
+ else {
+ $params->param( hafas => 1 );
+ }
+ $params = $params->to_string;
+ $self->redirect_to("/${input}?${params}");
+ }
+}
+
+1;
diff --git a/lib/DBInfoscreen/Controller/Wagenreihung.pm b/lib/DBInfoscreen/Controller/Wagenreihung.pm
new file mode 100644
index 0000000..b9f0ee3
--- /dev/null
+++ b/lib/DBInfoscreen/Controller/Wagenreihung.pm
@@ -0,0 +1,267 @@
+package DBInfoscreen::Controller::Wagenreihung;
+
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'Mojolicious::Controller';
+use Mojo::JSON qw(decode_json encode_json);
+use Mojo::Util qw(b64_encode b64_decode);
+
+use utf8;
+
+use Travel::Status::DE::DBRIS::Formation;
+
+sub handle_wagenreihung_error {
+ my ( $self, $train, $err ) = @_;
+
+ $self->render(
+ 'wagenreihung',
+ title => $train,
+ wr_error => $err,
+ wr => undef,
+ wref => undef,
+ hide_opts => 1,
+ status => 500,
+ );
+}
+
+sub wagenreihung {
+ my ($self) = @_;
+ my $exit_side = $self->param('e');
+
+ my $train_type = $self->param('category');
+ my $train_no = $self->param('number');
+ my $train = "${train_type} ${train_no}";
+
+ $self->render_later;
+
+ $self->wagonorder->get_p( param => $self->req->query_params->to_hash )
+ ->then(
+ sub {
+ my ($json) = @_;
+ my $wr;
+ eval {
+ $wr
+ = Travel::Status::DE::DBRIS::Formation->new( json => $json );
+ };
+ if ($@) {
+ $self->handle_wagenreihung_error( $train, scalar $@ );
+ return;
+ }
+
+ if ( $exit_side and $exit_side =~ m{^a} ) {
+ if ( $wr->sectors and defined $wr->direction ) {
+ my $section_0 = ( $wr->sectors )[0];
+ my $direction = $wr->direction;
+ if ( $section_0->name eq 'A' and $direction == 0 ) {
+ $exit_side =~ s{^a}{};
+ }
+ elsif ( $section_0->name ne 'A' and $direction == 100 ) {
+ $exit_side =~ s{^a}{};
+ }
+ else {
+ $exit_side = ( $exit_side eq 'ar' ) ? 'l' : 'r';
+ }
+ }
+ else {
+ $exit_side = undef;
+ }
+ }
+
+ my $wref = {
+ e => $exit_side ? substr( $exit_side, 0, 1 ) : '',
+ tt => $wr->train_type,
+ tn => $train_no,
+ p => $wr->platform
+ };
+
+ #if ( $wr->has_bad_wagons ) {
+
+ # # create fake positions as the correct ones are not available
+ # my $pos = 0;
+ # for my $wagon ( $wr->wagons ) {
+ # $wagon->{position}{start_percent} = $pos;
+ # $wagon->{position}{end_percent} = $pos + 4;
+ # $pos += 4;
+ # }
+ #}
+ if ( defined $wr->direction and scalar $wr->carriages > 2 ) {
+
+ # wagenlexikon images only know one orientation. They assume
+ # that the second class (i.e., the wagon with the lowest
+ # wagon number) is in the leftmost carriage(s). We define the
+ # wagon with the lowest start_percent value to be leftmost
+ # and invert the direction passed on to $wref if it is not
+ # the wagon with the lowest wagon number.
+
+ # Note that we need to check both the first two and the last two
+ # wagons as the train may consist of several wings. If their
+ # order differs, we do not show a direction, as we do not
+ # handle that case yet.
+
+ my @wagons = $wr->carriages;
+
+ # skip first/last wagon as it may be a locomotive
+ my $wna1 = $wagons[1]->number;
+ my $wna2 = $wagons[2]->number;
+ my $wnb1 = $wagons[-3]->number;
+ my $wnb2 = $wagons[-2]->number;
+ my $wpa1 = $wagons[1]->start_percent;
+ my $wpa2 = $wagons[2]->start_percent;
+ my $wpb1 = $wagons[-3]->start_percent;
+ my $wpb2 = $wagons[-2]->start_percent;
+
+ if ( $wna1 =~ m{^\d+$}
+ and $wna2 =~ m{^\d+$}
+ and $wnb1 =~ m{^\d+$}
+ and $wnb2 =~ m{^\d+$} )
+ {
+
+ # We need to perform normalization in two cases:
+ # * wagon 1 is leftmost and its number is higher than wagon 2
+ # * wagon 1 is rightmost and its number is lower than wagon 2
+ # (-> the leftmost wagon has the highest number)
+
+ # However, if wpa/wna und wpb/wnb do not match, we have a
+ # winged train with different normalization requirements
+ # in its wings. We do not handle that case yet.
+ if ( ( $wna1 <=> $wna2 ) != ( $wnb1 <=> $wnb2 ) ) {
+
+ # unhandled. Do not set $wref->{d}.
+ }
+ elsif (( $wpa1 < $wpa2 and $wna1 > $wna2 )
+ or ( $wpa1 > $wpa2 and $wna1 < $wna2 ) )
+ {
+ # perform normalization
+ $wref->{d} = 100 - $wr->direction;
+ }
+ else {
+ # no normalization required
+ $wref->{d} = $wr->direction;
+ }
+ }
+ }
+
+ my $exit_dir = 'unknown';
+ if ( defined $wr->direction and $exit_side ) {
+ if ( $wr->direction == 0 and $exit_side eq 'l' ) {
+ $exit_dir = 'left';
+ }
+ elsif ( $wr->direction == 0 and $exit_side eq 'r' ) {
+ $exit_dir = 'right';
+ }
+ elsif ( $wr->direction == 100 and $exit_side eq 'l' ) {
+ $exit_dir = 'right';
+ }
+ elsif ( $wr->direction == 100 and $exit_side eq 'r' ) {
+ $exit_dir = 'left';
+ }
+ }
+
+ $wref = b64_encode( encode_json($wref) );
+
+ my $title = join( ' / ', map { $_->{name} } $wr->trains );
+
+ $self->render(
+ 'wagenreihung',
+ description => sprintf( 'Ist-Wagenreihung %s', $title ),
+ wr_error => undef,
+ title => $title,
+ wr => $wr,
+ wref => $wref,
+ exit_dir => $exit_dir,
+ hide_opts => 1,
+ ts => $json->{ts},
+ );
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+
+ $self->handle_wagenreihung_error( $train,
+ $err // "Unbekannter Fehler" );
+ return;
+ }
+ )->wait;
+
+}
+
+sub wagen {
+ my ($self) = @_;
+ my $wagon_id = $self->stash('wagon');
+ my $wagon_no = $self->param('n');
+ my $section = $self->param('s');
+ my $wref = $self->param('r');
+
+ if ( not $self->app->dbdb_wagon->{$wagon_id} ) {
+ $self->render(
+ 'not_found',
+ message => "Keine Daten zu Wagentyp \"${wagon_id}\" vorhanden",
+ hide_opts => 1
+ );
+ return;
+ }
+
+ eval { $wref = decode_json( b64_decode($wref) ); };
+ if ($@) {
+ $wref = {};
+ }
+
+ $wref->{wn} = $wagon_no;
+ $wref->{ws} = $section;
+
+ my @wagon_files
+ = ("https://lib.finalrewind.org/dbdb/db_wagen/${wagon_id}.png");
+
+ if ( $self->app->dbdb_wagon->{"${wagon_id}_u"} ) {
+ @wagon_files = (
+ "https://lib.finalrewind.org/dbdb/db_wagen/${wagon_id}_u.png",
+ "https://lib.finalrewind.org/dbdb/db_wagen/${wagon_id}_l.png"
+ );
+ }
+
+ my $title = 'Wagen ' . $wagon_id;
+
+ if ( $wref->{tt} and $wref->{tn} ) {
+ $title = sprintf( '%s %s', $wref->{tt}, $wref->{tn} );
+ if ($wagon_no) {
+ $title .= ' Wagen ' . $wagon_no;
+ }
+ else {
+ $title .= ' Wagen ' . $wagon_id;
+ }
+ }
+
+ if ( defined $wref->{d} and $wref->{e} ) {
+ if ( $wref->{d} == 0 and $wref->{e} eq 'l' ) {
+ $wref->{e} = 'd';
+ }
+ elsif ( $wref->{d} == 0 and $wref->{e} eq 'r' ) {
+ $wref->{e} = 'u';
+ }
+ elsif ( $wref->{d} == 100 and $wref->{e} eq 'l' ) {
+ $wref->{e} = 'u';
+ }
+ elsif ( $wref->{d} == 100 and $wref->{e} eq 'r' ) {
+ $wref->{e} = 'd';
+ }
+ }
+ else {
+ $wref->{e} = '';
+ }
+
+ $self->render(
+ 'wagen',
+ description => ( $wref->{s} ? 'Position von ' : q{} )
+ . $title
+ . ( $wref->{s} ? " in $wref->{s}" : q{} ),
+ title => $title,
+ wagon_files => [@wagon_files],
+ wagon_data => $self->app->dbdb_wagon->{$wagon_id},
+ wref => $wref,
+ hide_opts => 1,
+ );
+}
+
+1;
diff --git a/lib/DBInfoscreen/Helper/DBRIS.pm b/lib/DBInfoscreen/Helper/DBRIS.pm
new file mode 100644
index 0000000..e780213
--- /dev/null
+++ b/lib/DBInfoscreen/Helper/DBRIS.pm
@@ -0,0 +1,93 @@
+package DBInfoscreen::Helper::DBRIS;
+
+# Copyright (C) 2025 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use strict;
+use warnings;
+use 5.020;
+
+use DateTime;
+use Encode qw(decode encode);
+use Travel::Status::DE::DBRIS;
+use Mojo::JSON qw(decode_json);
+use Mojo::Promise;
+use Mojo::UserAgent;
+
+sub new {
+ my ( $class, %opt ) = @_;
+
+ my $version = $opt{version};
+
+ $opt{header}
+ = { 'User-Agent' =>
+"dbf/${version} on $opt{root_url} +https://finalrewind.org/projects/db-fakedisplay"
+ };
+
+ return bless( \%opt, $class );
+
+}
+
+sub get_journey_p {
+ my ( $self, %opt ) = @_;
+
+ my $agent = $self->{user_agent};
+
+ if ( my $proxy = $ENV{DBFAKEDISPLAY_DBRIS_PROXY} ) {
+ $agent = Mojo::UserAgent->new;
+ $agent->proxy->http($proxy);
+ $agent->proxy->https($proxy);
+ }
+
+ return Travel::Status::DE::DBRIS->new_p(
+ journey => $opt{id},
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10)
+ );
+}
+
+# Input: TripID
+# Output: Promise returning a Travel::Status::DE::DBRIS::Journey instance on success
+sub get_polyline_p {
+ my ( $self, %opt ) = @_;
+
+ my $trip_id = $opt{id};
+ my $promise = Mojo::Promise->new;
+
+ my $agent = $self->{user_agent};
+
+ if ( my $proxy = $ENV{DBFAKEDISPLAY_DBRIS_PROXY} ) {
+ $agent = Mojo::UserAgent->new;
+ $agent->proxy->http($proxy);
+ $agent->proxy->https($proxy);
+ }
+
+ Travel::Status::DE::DBRIS->new_p(
+ journey => $trip_id,
+ with_polyline => 1,
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10)
+ )->then(
+ sub {
+ my ($dbris) = @_;
+ my $journey = $dbris->result;
+
+ $promise->resolve($journey);
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->{log}->debug("DBRIS->new_p($trip_id) error: $err");
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
+}
+
+1;
diff --git a/lib/DBInfoscreen/Helper/EFA.pm b/lib/DBInfoscreen/Helper/EFA.pm
new file mode 100644
index 0000000..0e7f7d7
--- /dev/null
+++ b/lib/DBInfoscreen/Helper/EFA.pm
@@ -0,0 +1,162 @@
+package DBInfoscreen::Helper::EFA;
+
+# Copyright (C) 2020-2022 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use strict;
+use warnings;
+use 5.020;
+
+use DateTime;
+use Encode qw(decode encode);
+use Mojo::JSON qw(decode_json);
+use Mojo::Promise;
+use Mojo::Util qw(url_escape);
+use Travel::Status::DE::EFA;
+
+sub new {
+ my ( $class, %opt ) = @_;
+
+ my $version = $opt{version};
+
+ $opt{header}
+ = { 'User-Agent' =>
+"dbf/${version} on $opt{root_url} +https://finalrewind.org/projects/db-fakedisplay"
+ };
+
+ return bless( \%opt, $class );
+
+}
+
+sub get_polyline_p {
+ my ( $self, %opt ) = @_;
+
+ my $stopseq = $opt{stopseq};
+ my $service = $opt{service};
+ my $promise = Mojo::Promise->new;
+
+ Travel::Status::DE::EFA->new_p(
+ service => $service,
+ stopseq => $stopseq,
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $self->{user_agent}->request_timeout(10)
+ )->then(
+ sub {
+ my ($efa) = @_;
+ my $journey = $efa->result;
+
+ $promise->resolve($journey);
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->{log}->debug("EFA->new_p($stopseq) error: $err");
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
+}
+
+sub get_coverage {
+ my ( $self, $service ) = @_;
+
+ my $service_definition = Travel::Status::DE::EFA::get_service($service);
+
+ if ( not $service_definition ) {
+ return {};
+ }
+
+ return $service_definition->{coverage}{area} // {};
+}
+
+sub get_json_p {
+ my ( $self, $cache, $url ) = @_;
+
+ my $promise = Mojo::Promise->new;
+
+ if ( my $content = $cache->thaw($url) ) {
+ $self->{log}->debug("efa->get_json_p($url): cached");
+ if ( $content->{error} ) {
+ return $promise->reject( $content->{error} );
+ }
+ return $promise->resolve($content);
+ }
+
+ $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} )
+ ->then(
+ sub {
+ my ($tx) = @_;
+
+ if ( my $err = $tx->error ) {
+ $self->{log}->debug(
+ "efa->get_json_p($url): HTTP $err->{code} $err->{message}");
+ $cache->freeze( $url, { error => $err->{message} } );
+ $promise->reject(
+ "GET $url returned HTTP $err->{code} $err->{message}");
+ return;
+ }
+
+ my $res = $tx->res->json;
+
+ if ( not $res ) {
+ $self->{log}->debug("efa->get_json_p($url): empty response");
+ $promise->reject("GET $url returned empty response");
+ return;
+ }
+
+ $cache->freeze( $url, $res );
+
+ $promise->resolve($res);
+
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->{log}->debug("efa->get_json_p($url): $err");
+ $cache->freeze( $url, { error => $err } );
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
+}
+
+sub get_efa_occupancy {
+ my ( $self, %opt ) = @_;
+
+ my $eva = $opt{eva};
+ my $train_no = $opt{train_no};
+ my $promise = Mojo::Promise->new;
+
+ $self->get_json_p( $self->{realtime_cache},
+ "https://vrrf.finalrewind.org/_eva/occupancy-by-eva/${eva}.json" )
+ ->then(
+ sub {
+ my ($utilization_json) = @_;
+
+ if ( $utilization_json->{train}{$train_no}{occupancy} ) {
+ $promise->resolve(
+ $utilization_json->{train}{$train_no}{occupancy} );
+ return;
+ }
+ $promise->reject;
+ return;
+ }
+ )->catch(
+ sub {
+ $promise->reject;
+ return;
+ }
+ )->wait;
+
+ return $promise;
+}
+
+1;
diff --git a/lib/DBInfoscreen/Helper/HAFAS.pm b/lib/DBInfoscreen/Helper/HAFAS.pm
new file mode 100644
index 0000000..e16bad8
--- /dev/null
+++ b/lib/DBInfoscreen/Helper/HAFAS.pm
@@ -0,0 +1,318 @@
+package DBInfoscreen::Helper::HAFAS;
+
+# Copyright (C) 2011-2022 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 encode);
+use Travel::Status::DE::HAFAS;
+use Mojo::JSON qw(decode_json);
+use Mojo::Promise;
+use Mojo::UserAgent;
+
+sub new {
+ my ( $class, %opt ) = @_;
+
+ my $version = $opt{version};
+
+ $opt{header}
+ = { 'User-Agent' =>
+"dbf/${version} on $opt{root_url} +https://finalrewind.org/projects/db-fakedisplay"
+ };
+
+ return bless( \%opt, $class );
+
+}
+
+sub get_coverage {
+ my ( $self, $service ) = @_;
+
+ my $service_definition = Travel::Status::DE::HAFAS::get_service($service);
+
+ if ( not $service_definition ) {
+ return {};
+ }
+
+ return $service_definition->{coverage}{area} // {};
+}
+
+sub get_route_p {
+ my ( $self, %opt ) = @_;
+
+ my $promise = Mojo::Promise->new;
+ my $now = DateTime->now( time_zone => 'Europe/Berlin' );
+
+ my $hafas_promise;
+
+ my $agent = $self->{user_agent};
+ if ( $opt{service} and $opt{service} eq 'PKP' ) {
+
+ # PKP needs proxying
+ $agent = Mojo::UserAgent->new;
+ }
+
+ if ( $opt{trip_id} ) {
+ $hafas_promise = Travel::Status::DE::HAFAS->new_p(
+ service => $opt{service} // 'ÖBB',
+ journey => {
+ id => $opt{trip_id},
+ },
+ language => $opt{language},
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10)
+ );
+ }
+ elsif ( $opt{train} ) {
+ $opt{train_req} = $opt{train}->type . ' ' . $opt{train}->train_no;
+ $opt{train_origin} = $opt{train}->origin;
+ }
+ else {
+ $opt{train_req} = $opt{train_type} . ' ' . $opt{train_no};
+ }
+
+ $hafas_promise //= Travel::Status::DE::HAFAS->new_p(
+ service => $opt{service} // 'ÖBB',
+ journeyMatch => $opt{train_req} =~ s{^- }{}r,
+ datetime => ( $opt{train} ? $opt{train}->start : $opt{datetime} ),
+ language => $opt{language},
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10)
+ )->then(
+ sub {
+ my ($hafas) = @_;
+ my @results = $hafas->results;
+
+ if ( not @results ) {
+ return Mojo::Promise->reject(
+ "journeyMatch($opt{train_req}) found no results");
+ }
+
+ my $result = $results[0];
+ if ( @results > 1 ) {
+ for my $journey (@results) {
+ if ( $opt{train_origin}
+ and ( $journey->route )[0]->loc->name eq
+ $opt{train_origin} )
+ {
+ $result = $journey;
+ last;
+ }
+ }
+ }
+
+ return Travel::Status::DE::HAFAS->new_p(
+ service => $opt{service} // 'ÖBB',
+ journey => {
+ id => $result->id,
+ },
+ language => $opt{language},
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10)
+ );
+ }
+ );
+
+ $hafas_promise->then(
+ sub {
+ my ($hafas) = @_;
+ my $journey = $hafas->result;
+ my @ret;
+ my $station_is_past = 1;
+
+ my $num_names = 0;
+ my $prev_name = q{};
+ my $num_directions = 0;
+ my $prev_direction = q{};
+ my $num_operators = 0;
+ my $prev_operator = q{};
+
+ for my $stop ( $journey->route ) {
+ my $prod = $stop->prod_dep // $stop->prod_arr;
+ if ( $prod and $prod->name and $prod->name ne $prev_name ) {
+ $num_names++;
+ $prev_name = $prod->name;
+ }
+ if ( $prod
+ and $prod->operator
+ and $prod->operator ne $prev_operator )
+ {
+ $num_operators++;
+ $prev_operator = $prod->operator;
+ }
+ if ( $stop->direction and $stop->direction ne $prev_direction )
+ {
+ $num_directions++;
+ $prev_direction = $stop->direction;
+ }
+ }
+
+ $prev_name = q{};
+ $prev_direction = q{};
+ $prev_operator = q{};
+
+ for my $stop ( $journey->route ) {
+
+ my $prod = $stop->prod_dep // $stop->prod_arr;
+ my %annotation;
+ if ( $num_names > 1
+ and $prod
+ and $prod->name
+ and $prod->name ne $prev_name )
+ {
+ $prev_name = $annotation{prod_name} = $prod->name;
+ }
+ if ( $num_operators > 1
+ and $prod
+ and $prod->operator
+ and $prod->operator ne $prev_operator )
+ {
+ $prev_operator = $annotation{operator} = $prod->operator;
+ }
+ if ( $num_directions > 1
+ and $stop->direction
+ and $stop->direction ne $prev_direction )
+ {
+ $prev_direction = $annotation{direction} = $stop->direction;
+ }
+
+ if (%annotation) {
+ $annotation{is_annotated} = 1;
+ }
+
+ push(
+ @ret,
+ {
+ name => $stop->loc->name,
+ eva => $stop->loc->eva,
+ sched_arr => $stop->sched_arr,
+ sched_dep => $stop->sched_dep,
+ rt_arr => $stop->rt_arr,
+ rt_dep => $stop->rt_dep,
+ arr_delay => $stop->arr_delay,
+ dep_delay => $stop->dep_delay,
+ arr_cancelled => $stop->arr_cancelled,
+ dep_cancelled => $stop->dep_cancelled,
+ tz_offset => $stop->tz_offset,
+ platform => $stop->platform,
+ sched_platform => $stop->sched_platform,
+ load => $stop->load,
+ isAdditional => $stop->is_additional,
+ isCancelled => (
+ ( $stop->arr_cancelled or not $stop->sched_arr )
+ and
+ ( $stop->dep_cancelled or not $stop->sched_dep )
+ ),
+ %annotation,
+ }
+ );
+ if (
+ $station_is_past
+ and not $ret[-1]{isCancelled}
+ and $now->epoch < (
+ $ret[-1]{rt_arr} // $ret[-1]{rt_dep}
+ // $ret[-1]{sched_arr} // $ret[-1]{sched_dep} // $now
+ )->epoch
+ )
+ {
+ $station_is_past = 0;
+ }
+ $ret[-1]{isPast} = $station_is_past;
+ if ( $stop->tz_offset ) {
+ if ( $stop->sched_arr ) {
+ $ret[-1]{local_sched_arr}
+ = $stop->sched_arr->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->sched_dep ) {
+ $ret[-1]{local_sched_dep}
+ = $stop->sched_dep->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->rt_arr ) {
+ $ret[-1]{local_rt_arr} = $stop->rt_arr->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ if ( $stop->rt_dep ) {
+ $ret[-1]{local_rt_dep} = $stop->rt_dep->clone->add(
+ minutes => $stop->tz_offset );
+ }
+ $ret[-1]{local_dt_ad} = $ret[-1]{local_rt_arr}
+ // $ret[-1]{local_sched_arr} // $ret[-1]{local_rt_dep}
+ // $ret[-1]{local_sched_dep};
+ $ret[-1]{local_dt_da} = $ret[-1]{local_rt_dep}
+ // $ret[-1]{local_sched_dep} // $ret[-1]{local_rt_arr}
+ // $ret[-1]{local_sched_arr};
+ }
+ }
+
+ $promise->resolve( \@ret, $journey, $hafas );
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
+}
+
+# Input: (HAFAS TripID, line number)
+# Output: Promise returning a Travel::Status::DE::HAFAS::Journey instance on success
+sub get_polyline_p {
+ my ( $self, %opt ) = @_;
+
+ my $trip_id = $opt{id};
+ my $line = $opt{line};
+ my $service = $opt{service} // 'ÖBB';
+ my $promise = Mojo::Promise->new;
+
+ my $agent = $self->{user_agent};
+ if ( $opt{service} and $opt{service} eq 'PKP' ) {
+
+ # PKP needs proxying
+ $agent = Mojo::UserAgent->new;
+ }
+
+ Travel::Status::DE::HAFAS->new_p(
+ service => $service,
+ journey => {
+ id => $trip_id,
+ name => $line,
+ },
+ with_polyline => 1,
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10)
+ )->then(
+ sub {
+ my ($hafas) = @_;
+ my $journey = $hafas->result;
+
+ $promise->resolve($journey);
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->{log}->debug("HAFAS->new_p($trip_id, $line) error: $err");
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
+}
+
+1;
diff --git a/lib/DBInfoscreen/Helper/MOTIS.pm b/lib/DBInfoscreen/Helper/MOTIS.pm
new file mode 100644
index 0000000..002a601
--- /dev/null
+++ b/lib/DBInfoscreen/Helper/MOTIS.pm
@@ -0,0 +1,82 @@
+package DBInfoscreen::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 DateTime;
+use Encode qw(decode encode);
+use Travel::Status::MOTIS;
+use Mojo::JSON qw(decode_json);
+use Mojo::Promise;
+
+sub new {
+ my ( $class, %opt ) = @_;
+
+ my $version = $opt{version};
+
+ $opt{header}
+ = { 'User-Agent' =>
+"dbf/${version} on $opt{root_url} +https://finalrewind.org/projects/db-fakedisplay"
+ };
+
+ return bless( \%opt, $class );
+
+}
+
+sub get_coverage {
+ my ( $self, $service ) = @_;
+
+ my $service_definition = Travel::Status::MOTIS::get_service($service);
+
+ if ( not $service_definition ) {
+ return {};
+ }
+
+ return $service_definition->{coverage}{area} // {};
+}
+
+# Input: TripID
+# Output: Promise returning a Travel::Status::MOTIS::Trip instance on success
+sub get_polyline_p {
+ my ( $self, %opt ) = @_;
+
+ my $trip_id = $opt{id};
+ my $service = $opt{service} // 'transitous';
+
+ my $promise = Mojo::Promise->new;
+
+ my $agent = $self->{user_agent};
+
+ Travel::Status::MOTIS->new_p(
+ cache => $self->{realtime_cache},
+ promise => 'Mojo::Promise',
+ user_agent => $agent->request_timeout(10),
+
+ service => $service,
+ trip_id => $trip_id,
+ )->then(
+ sub {
+ my ($motis) = @_;
+ my $trip = $motis->result;
+
+ $promise->resolve($trip);
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->{log}->debug("MOTIS->new_p($trip_id) error: $err");
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+
+ return $promise;
+}
+
+1;
diff --git a/lib/DBInfoscreen/Helper/Wagonorder.pm b/lib/DBInfoscreen/Helper/Wagonorder.pm
new file mode 100644
index 0000000..9981244
--- /dev/null
+++ b/lib/DBInfoscreen/Helper/Wagonorder.pm
@@ -0,0 +1,154 @@
+package DBInfoscreen::Helper::Wagonorder;
+
+# Copyright (C) 2011-2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use strict;
+use warnings;
+use 5.020;
+
+use DateTime;
+use Mojo::Promise;
+
+sub new {
+ my ( $class, %opt ) = @_;
+
+ my $version = $opt{version};
+
+ $opt{header}
+ = { 'User-Agent' =>
+"dbf/${version} on $opt{root_url} +https://finalrewind.org/projects/db-fakedisplay"
+ };
+
+ return bless( \%opt, $class );
+
+}
+
+sub get_p {
+ my ( $self, %opt ) = @_;
+
+ my %param;
+
+ if ( $opt{param} ) {
+ %param = %{ $opt{param} };
+ delete $param{e};
+ }
+ else {
+ my $datetime = $opt{datetime}->clone->set_time_zone('UTC');
+ %param = (
+ administrationId => 80,
+ category => $opt{train_type},
+ date => $datetime->strftime('%Y-%m-%d'),
+ evaNumber => $opt{eva},
+ number => $opt{train_number},
+ time => $datetime->rfc3339 =~ s{(?=Z)}{.000}r
+ );
+ }
+
+ my $url = sprintf( '%s?%s',
+'https://www.bahn.de/web/api/reisebegleitung/wagenreihung/vehicle-sequence',
+ join( '&', map { $_ . '=' . $param{$_} } sort keys %param ) );
+
+ my $promise = Mojo::Promise->new;
+
+ if ( my $content = $self->{main_cache}->thaw($url) ) {
+ $self->{log}->debug("wagonorder->get_p($url): cached");
+ if ( $content->{error} ) {
+ return $promise->reject(
+"GET $url: HTTP $content->{error}{code} $content->{error}{message} (cachd)"
+ );
+ }
+ return $promise->resolve( $content, \%param );
+ }
+
+ if ( my $content = $self->{realtime_cache}->thaw($url) ) {
+ $self->{log}->debug("wagonorder->get_p($url): cached");
+ if ( $content->{error} ) {
+ return $promise->reject(
+"GET $url: HTTP $content->{error}{code} $content->{error}{message} (cachd)"
+ );
+ }
+ return $promise->resolve( $content, \%param );
+ }
+
+ $self->{user_agent}->request_timeout(10)->get_p( $url => $self->{header} )
+ ->then(
+ sub {
+ my ($tx) = @_;
+
+ if ( my $err = $tx->error ) {
+ my $json = {
+ error => {
+ id => $err->{code},
+ msg => $err->{message}
+ }
+ };
+ $self->{log}->debug(
+ "wagonorder->get_p($url): HTTP $err->{code} $err->{message}"
+ );
+ $self->{realtime_cache}->freeze( $url, $json );
+ $promise->reject("GET $url: HTTP $err->{code} $err->{message}");
+ return;
+ }
+
+ $self->{log}->debug("wagonorder->get_p($url): OK");
+ my $json = $tx->res->json;
+ $json->{ts} = DateTime->now( time_zone => 'Europe/Berlin' )
+ ->strftime('%d.%m.%Y %H:%M');
+
+ $self->{main_cache}->freeze( $url, $json );
+ $promise->resolve( $json, \%param );
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $self->{log}->warn("wagonorder->get_p($url): $err");
+ $promise->reject("GET $url: $err");
+ return;
+ }
+ )->wait;
+ return $promise;
+}
+
+sub get_stationinfo_p {
+ my ( $self, $eva ) = @_;
+
+ my $url = "https://lib.finalrewind.org/dbdb/s/${eva}.json";
+
+ my $cache = $self->{main_cache};
+ my $promise = Mojo::Promise->new;
+
+ if ( my $content = $cache->thaw($url) ) {
+ return $promise->resolve($content);
+ }
+
+ $self->{user_agent}->request_timeout(5)->get_p( $url => $self->{header} )
+ ->then(
+ sub {
+ my ($tx) = @_;
+
+ if ( my $err = $tx->error ) {
+ $cache->freeze( $url, {} );
+ $promise->reject("HTTP $err->{code} $err->{message}");
+ return;
+ }
+
+ my $json = $tx->result->json;
+ $cache->freeze( $url, $json );
+ $promise->resolve($json);
+ return;
+ }
+ )->catch(
+ sub {
+ my ($err) = @_;
+ $cache->freeze( $url, {} );
+ $promise->reject($err);
+ return;
+ }
+ )->wait;
+ return $promise;
+}
+
+1;
diff --git a/lib/DBInfoscreen/I18N/en.pm b/lib/DBInfoscreen/I18N/en.pm
new file mode 100644
index 0000000..3abb70f
--- /dev/null
+++ b/lib/DBInfoscreen/I18N/en.pm
@@ -0,0 +1,84 @@
+package DBInfoscreen::I18N::en;
+
+# Copyright (C) 2023 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+use Mojo::Base 'DBInfoscreen::I18N';
+
+our %Lexicon = (
+
+ # common
+ 'Stationen in der Umgebung suchen' => 'Find stops nearby',
+
+ # layouts/app
+ 'Mehrdeutige Eingabe' => 'Ambiguous input',
+ 'Bitte eine Station aus der Liste auswählen' =>
+ 'Please select a station from the list',
+ 'Zug / Station' => 'Enter train number or station name',
+ 'Zug, Stationsname oder Ril100-Kürzel' =>
+ 'train, station name, or DS100 code',
+ 'Abfahrtstafel' => 'Show departures',
+ 'Weitere Einstellungen' => 'Preferences',
+ 'Zeiten inkl. Verspätung angeben' => 'Include delay in timestamps',
+ 'Verspätungen erst ab 5 Minuten anzeigen' => 'Hide delays below 5 minutes',
+ 'Mehr Details' => 'Verbose mode',
+'Betriebliche Bahnhofstrennungen berücksichtigen (z.B. "Hbf (Fern+Regio)" vs. "Hbf (S)")'
+ => 'Respect split stations; do not join them',
+ 'Bereits abgefahrene Züge anzeigen' => 'Include past trains',
+ 'Formular verstecken' => 'Hide form',
+ 'Nur Züge über' => 'Only show trains via',
+ 'Bahnhof 1, Bhf2, ... (oder regulärer Ausdruck)' =>
+ 'Station 1, 2, ... (or regular expression)',
+ 'Gleise' => 'Platforms',
+ 'Ankunfts- oder Abfahrtszeit anzeigen?' => 'Show arrival or departure?',
+ 'Abfahrt bevorzugen' => 'prefer departure',
+ 'Nur Abfahrt' => 'departure only',
+ 'Nur Ankunft' => 'arrival only',
+ 'Anzeigen' => 'Submit',
+ 'Datenschutz' => 'Privacy',
+ 'Impressum' => 'Imprint',
+
+ # landing page
+ 'Oder hier angeben:' => 'Or enter manually:',
+
+ # train details
+ 'Gleis' => 'Platform',
+ 'An:' => 'Arr',
+ 'Ab:' => 'Dep',
+ 'Plan:' => 'Sched',
+ 'Auslastung unbekannt' => 'Occupancy unknown',
+ 'Geringe Auslastung' => 'Low occupancy',
+ 'Hohe Auslastung' => 'High occupancy',
+ 'Sehr hohe Auslastung' => 'Very high occupancy',
+ 'Zug ist ausgebucht' => 'Fully booked',
+ 'Geringe Auslastung erwartet' => 'Low occupancy expected',
+ 'Hohe Auslastung erwartet' => 'High occupancy expected',
+ 'Sehr hohe Auslastung erwartet' => 'Very high occupancy expected',
+ 'Meldungen' => 'Messages',
+ 'Fahrtverlauf am' => 'Route on',
+ 'Betrieb' => 'Operator',
+ 'Karte' => 'Map',
+ 'Wagen' => 'Composition',
+
+ # wagon order
+ 'Nach' => 'To',
+ 'in Abschnitt' => 'in sections',
+ 'Wagen ' => 'carriage ',
+
+ # map
+ 'Fahrt' => 'Trip',
+ 'von' => 'from',
+ 'nach' => 'to',
+ 'Nächster Halt:' => 'Next stop:',
+ 'um' => 'at',
+ 'auf Gleis' => 'on platform',
+ 'Aufenthalt in' => 'Stopped in',
+ 'an Gleis' => 'on platform',
+ 'bis' => 'until',
+ 'Abfahrt in' => 'Departs',
+ 'von Gleis' => 'from platform',
+ 'Endstation erreicht um' => 'Terminus reached at',
+);
+
+1;
diff --git a/public/collapse.js b/public/collapse.js
deleted file mode 100644
index d38b30a..0000000
--- a/public/collapse.js
+++ /dev/null
@@ -1,22 +0,0 @@
-$(document).ready(function() {
- $('div.displayclean > ul > li').each(function() {
- $(this).click(function() {
- $(this).children('.moreinfo').each(function() {
- if ($(this).hasClass('expanded-moreinfo')) {
- $(this).removeClass('expanded-moreinfo');
- $(this).addClass('collapsed-moreinfo');
- }
- else {
- $('.moreinfo').each(function() {
- if ($(this).hasClass('expanded-moreinfo')) {
- $(this).removeClass('expanded-moreinfo');
- $(this).addClass('collapsed-moreinfo');
- }
- });
- $(this).removeClass('collapsed-moreinfo');
- $(this).addClass('expanded-moreinfo');
- }
- });
- });
- });
-});
diff --git a/public/default.css b/public/default.css
deleted file mode 100644
index 8676a2d..0000000
--- a/public/default.css
+++ /dev/null
@@ -1,446 +0,0 @@
-html {
- font-family: Sans-Serif;
-}
-
-div.displayclean {
- background-color:#F8F8F8;
- border-width:1px 2px;
- width:100%;
- margin-bottom: 5em;
-}
-
-div.displayclean > ul {
- position:relative;
- width:100%;
- background-color:#F8F8F8;
-
- list-style-type:none;
- margin:0;
- padding:0;
- border-bottom: 1px solid #cccccc;
-}
-
-div.displayclean > ul > li {
- min-height:7em;
- background-color:#F8F8F8;
- display:block;
- border-width:1px 0;
- border-style:solid;
- border-color:#CCCCCC;
- width:100%;
- position:relative;
-}
-
-div.displayclean li .line {
- color:#FFFFFF;
- background-color:#666666;
- font-weight:bold;
- font-size: 2.9em;
- padding:3px 8px 2px 5px;
- position:absolute;
- top:7px;
- left:2px;
-}
-
-div.displayclean li .line .trainno {
- font-weight: normal;
-}
-
-div.displayclean li .lineinfo {
- color:#000000;
- font-size: 2em;
- position:absolute;
- bottom:2px;
- left:2px;
-}
-
-div.displayclean li .lineinfo .replacement {
- color: #006600;
-}
-
-div.displayclean li .lineinfo .replaced {
- color: #660000;
-}
-
-div.displayclean li .tram {
- background-color: #CC0000;
-}
-
-div.displayclean li .sbahn {
- background-color:#006E10;
- -webkit-border-radius: 30px;
- -moz-border-radius: 30px;
- border-radius: 30px;
-}
-
-div.displayclean li .ubahn {
- background-color:#001090;
-}
-
-div.displayclean li .bus {
- background-color:#991199;
- -webkit-border-radius: 10px;
- -moz-border-radius: 10px;
- border-radius: 10px;
-}
-
-div.displayclean li .route {
- color:#444444;
- font-size:2.1em;
- position:absolute;
- top:5px;
- left:7.7em;
- height: 1em;
- width: 70%;
- overflow: hidden;
-}
-
-div.displayclean li .info {
- color:#ff0000;
- font-size:2.1em;
- position:absolute;
- top:5px;
- left:7.7em;
- height: 1.1em;
- width: 70%;
- overflow: hidden;
- z-index: 2;
-}
-
-div.displayclean li .moreinfo {
- color:#000000;
- font-size:2.1em;
- position:fixed;
- top:0em;
- padding-top: 1em;
- left:0em;
- padding-left: 1em;
- right:0em;
- padding-right: 1em;
- bottom:0em;
- background-color: white;
- z-index: 5;
- display: none;
- overflow: auto;
-}
-
-div.displayclean li .collapsed-moreinfo {
- display: none;
-}
-
-div.displayclean li .expanded-moreinfo {
- display: block;
-}
-
-div.displayclean li .moreinfo .mheader {
- text-align: center;
- background-color: #dddddd;
- font-size: 120%;
-}
-
-div.displayclean li .moreinfo .reason,
-div.displayclean li .moreinfo .minfo {
- color: #ff0000;
- margin-bottom: 0.6em;
-}
-
-div.displayclean li .moreinfo .timeinfo {
- margin-bottom: 0.6em;
-}
-
-div.displayclean li .moreinfo .mroute {
- margin-bottom: 0.6em;
-}
-
-div.displayclean li .moreinfo .mroute .separator {
- color: #999999;
-}
-
-div.displayclean li .moreinfo .mroute .important-stop {
- color: #000000;
-}
-
-div.displayclean li .moreinfo .mroute .generic-stop {
- color: #555555;
-}
-
-div.displayclean li .moreinfo .mroute .additional-stop {
- color: #009900;
-}
-
-div.displayclean li .moreinfo .mroute .cancelled-stop {
- color: #cc0000;
-}
-
-div.displayclean li .dest {
- color:#000000;
- /*font-weight:bold;*/
- font-size:4em;
- position:absolute;
- top:0.65em;
- left:4em;
- width: 70%;
- z-index: 1;
- overflow: hidden;
-}
-
-div.displayclean li.cancelled {
- background-color: #ffe7d0;
-}
-
-div.displayclean li .countdown {
- color: #000000;
- font-size: 3em;
- position: absolute;
- right: 5px;
- bottom: 2px;
-}
-
-div.displayclean li .header {
- color:#000000;
- font-size:2em;
- font-weight:bold;
- padding-top:8px;
- border-width-top:0;
- display:block;
- text-align:center;
-}
-
-div.displayclean li .head {
- border-bottom-width:0;
-}
-
-div.displayclean li .countdown .delay {
- font-size:1em;
- color:#FF0000;
- padding-right:7px;
-}
-
-div.displayclean li .countdown .undelay {
- font-size:1em;
- color:#006600;
- padding-right:7px;
-}
-
-div.displayclean li .countdown .delaynorm {
- font-size:0.9em;
- color:#BB3333;
- padding-right:7px;
-}
-
-div.displayclean li .countdown .undelaynorm {
- font-size:0.9em;
- color:#338833;
- padding-right:7px;
-}
-
-div.displayclean li .countdown .platform {
- font-weight: bold;
-}
-
-div.displayclean li .countdown .changed-platform {
- color:#ff0000;
-}
-
-div.displayclean li .time {
- color:#000000;
- font-size:2.4em;
- position:absolute;
- right:5px;
- top:5px;
-}
-
-div.displayclean span.delayed {
- color: #ff0000;
-}
-
-div.displaymulti {
- border: 0.2em solid #000066;
- width: 55em;
-}
-
-div.displaymulti div.display {
- background-color: #0000ff;
- color: white;
- font-family: Sans-Serif;
- font-weight: bold;
- position: relative;
- margin-bottom: 0;
- margin-top: 0;
- padding-top: 0;
- padding-bottom: 0;
- width: 55em;
- height: 1.4em;
-}
-
-div.displaymulti div.display div {
- overflow: hidden;
- position: absolute;
- height: 100%;
-}
-
-div.displaymulti div.time {
- left: 0;
- width: 6%;
- font-size: 95%;
-}
-
-div.displaymulti div.train {
- left: 5%;
- width: 9%;
- background-color: white;
- color: #0000ff;
- font-size: 95%;
-}
-
-div.displaymulti div.via {
- left: 15%;
- width: 35%;
-}
-
-div.displaymulti div.via span {
- margin-right: 0.4em;
- font-size: 80%;
-}
-
-div.displaymulti div.destination {
- left: 50%;
- width: 25%;
- font-size: 120%;
-}
-
-div.displaymulti div.platform {
- left: 75%;
- width: 5%;
-}
-
-div.displaymulti div.info {
- left: 80%;
- width: 20%;
- background-color: white;
- color: #0000ff;
- font-size: 80%;
- line-height: 150%;
-}
-
-div.displaymulti div.separator {
- border-bottom: 0.1em solid #000066;
-}
-
-div.displaysingle div.display {
- background-color: #0000ff;
- color: white;
- font-family: Sans-Serif;
- font-weight: bold;
- position: relative;
- margin-left: 1em;
- margin-top: 1em;
- float: left;
- width: 28em;
- height: 4.5em;
- border: 0.7em solid #000066;
-}
-
-div.displaysingle div.display div {
- overflow: hidden;
- position: absolute;
-}
-
-div.displaysingle div.no_data {
- top: 0.5em;
- left: 1em;
- font-size: 1.7em;
-}
-
-div.displaysingle div.time {
- top: 0em;
- left: 0em;
- font-size: 1.7em;
-}
-
-div.displaysingle div.train {
- left: 0em;
- top: 1.8em;
-}
-
-div.displaysingle div.via {
- top: 1.5em;
- left: 5.8em;
- width: 17em;
- height: 1em;
-}
-
-div.displaysingle div.via span {
- margin-right: 0.4em;
-}
-
-div.displaysingle div.destination {
- top: 1.6em;
- left: 3.6em;
- width: 12em;
- font-size: 1.6em;
- height: 1.2em;
-}
-
-div.displaysingle div.platform {
- top: 0em;
- right: 0em;
- font-size: 3em;
-}
-
-div.displaysingle div.info {
- top: 0em;
- left: 5.8em;
- width: 16.5em;
- height: 1em;
- background-color: white;
- color: #0000ff;
-}
-
-div.about {
- font-family: Sans-Serif;
- color: #666666;
-}
-
-div.about a {
- color: #000066;
- text-decoration: none;
-}
-
-div.error {
- font-size: 150%;
- font-weight: bold;
- color: #ee0000;
-}
-
-pre {
- margin-bottom: 2em;
-}
-
-span.optional,
-span.notes {
- color: #666666;
-}
-
-div.break {
- height: 1em;
-}
-
-div.field {
- margin-top: 0.3em;
- margin-bottom: 0.6em;
-}
-
-input, select {
- width: 20em;
- max-width: 100%;
- min-height: 1.8em;
-}
-
-div.notes {
- margin-top: 4em;
-}
-
-div.notes ul {
- margin-top: 1em;
-}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..fc18bfc
--- /dev/null
+++ b/public/favicon.ico
Binary files differ
diff --git a/public/jquery-1.10.2.min.js b/public/jquery-1.10.2.min.js
deleted file mode 100644
index da41706..0000000
--- a/public/jquery-1.10.2.min.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
-//@ sourceMappingURL=jquery-1.10.2.min.map
-*/
-(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t
-}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle);
-u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
diff --git a/public/mobile.css b/public/mobile.css
deleted file mode 100644
index bd03fb4..0000000
--- a/public/mobile.css
+++ /dev/null
@@ -1,23 +0,0 @@
-body {
- margin: 0;
-}
-
-div.displayclean > ul > li {
- font-size: 35%;
-}
-
-div.displayclean li .moreinfo {
- font-size: 2.6em;
-}
-
-p,
-div.input-field,
-div.notes {
- max-width: 94%;
- margin-left: auto;
- margin-right: auto;
-}
-
-p {
- text-align: justify;
-}
diff --git a/public/static/css/dark.min.css b/public/static/css/dark.min.css
new file mode 100644
index 0000000..3809a85
--- /dev/null
+++ b/public/static/css/dark.min.css
@@ -0,0 +1 @@
+body{margin:0;color:#fff;background-color:#101010}html{font-family:"Arimo", "Arial", Sans-Serif}a{color:#99f;text-decoration:none}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}p,div.about,div.config,div.input-field,div.notes{max-width:94%;margin-left:auto;margin-right:auto}div.journey,div.nextstop{max-width:98%;margin-left:auto;margin-right:auto}p{text-align:justify}div.content{width:100%;margin:0}.copyright{margin-top:1em;color:#999;clear:both}.wagonorder{position:relative;width:100%;height:100ex}.wagonorder.exit-unknown .section{left:1em;width:2em}.wagonorder.exit-unknown .wagon{left:3em;min-width:6em}.wagonorder.exit-unknown .details{left:10em;right:0em}.wagonorder.exit-left .section{left:1em;width:2em;background-color:#222}.wagonorder.exit-left .wagon{left:3em;min-width:6em}.wagonorder.exit-left .details{left:10em;right:0em}.wagonorder.exit-right .section{right:1em;width:2em;background-color:#222}.wagonorder.exit-right .wagon{right:3em;min-width:6em}.wagonorder.exit-right .details{right:10em;left:0em;text-align:right}.wagonorder .section{position:absolute;text-align:center}.wagonorder .wagon{position:absolute;border:1px solid #999;padding-left:0.2em;padding-right:0.2em}.wagonorder .wagon .material-icons{color:#bbb}.wagonorder .wagon .direction{position:absolute;left:0.2em;bottom:0;right:0;text-align:center;color:#bbb}.wagonorder .wagon~.wagon{border-top:none}.wagonorder .firstclass{background-color:#330}.wagonorder .powercar{background-color:#222}.wagonorder .closed{background-color:#222}.wagonorder .nondestwagon{border-style:dashed}.wagonorder .details{position:absolute;padding-top:0.5ex}.wagonorder .details .type{display:inline-block;width:5em;color:#fff}.wagonorder .details a.type{color:#99f}.wagonorder .details .groupno{color:#fff}.wagonorder .details .grouptype{color:#bbb}.wagonorder .details .grouptype:before{content:"("}.wagonorder .details .grouptype:after{content:")"}.wagonorder .details .uicunknown{color:#999}.wagonorder .details .uicexchange{margin-right:0.2em;color:#999}.wagonorder .details .uiccountry{margin-right:0.2em;color:#999}.wagonorder .details .uic5{margin-right:0.2em;color:#999}.wagonorder .details .uic56{color:#bbb;font-weight:bold}.wagonorder .details .uic78{margin-right:0.2em;color:#bbb;font-weight:bold}.wagonorder .details .uic78:before{content:"-"}.wagonorder .details .uictype{margin-right:0.2em;color:#bbb;font-weight:bold}.wagonorder .details .uicno{color:#bbb}.wagonorder .details .uiccheck{color:#999}.wagonorder .details .uiccheck:before{content:"-"}.singlewagon .sign-left{float:left;padding-left:5%}.singlewagon .sign-right{float:right;padding-right:5%}.singlewagon .sign-center{text-align:center}.singlewagon .platform{text-align:center;background-color:#444;font-weight:bold;padding-top:0.5em;padding-bottom:0.5em}.singlewagon img.wagonfile{width:100%;margin-top:0.2em;margin-bottom:0.2em}div.app{border-width:1px 2px;width:100%;margin-bottom:5em}div.app>ul{position:relative;width:100%;list-style-type:none;margin:0;padding:0}div.app>ul>li{min-height:7em;display:block;width:100%;position:relative;border-bottom:1px solid #999;background-color:#101010}div.app>ul>li.cancelled{background-color:#512f00}div.app>ul>li.cancelled .time{color:#fff !important}div.app>ul>li.past{opacity:0.8;background-color:#222}div.app>ul>li>a{color:#fff}div.app>ul>li .anchor{position:relative;top:-12em}div.app>ul>li .line{font-size:2.7em;position:absolute;bottom:5px;left:2px;max-width:6em;max-height:3ex;overflow:hidden}div.app>ul>li .line .trainno{font-weight:normal}div.app>ul>li .line .trainno_sub{font-weight:normal;font-size:0.6em;text-align:center;margin-top:-0.2em}div.app>ul>li .sbahn .trainno_sub{font-weight:normal;font-size:0.5em;text-align:center;margin-top:-0.25em}div.app>ul>li .lineinfo{color:#fff;font-size:2em;position:absolute;top:0px;left:2px}div.app>ul>li .route,div.app>ul>li .info{background-color:transparent;font-size:2.1em;position:absolute;top:0;left:7.7em;right:7em;height:1.5em;overflow:hidden;white-space:nowrap}div.app>ul>li .route{color:#ddd}div.app>ul>li .info{color:#f77}div.app>ul>li .dest,div.app>ul>li .origin{background-color:transparent;font-size:4em;position:absolute;bottom:0;left:4em;width:70%;white-space:nowrap;overflow:hidden;color:#fff}div.app>ul>li .dest{background-color:transparent;color:#fff}div.app>ul>li .origin{background-color:transparent;color:#bbb}div.app>ul>li .origin:before{content:"von "}div.app>ul>li .load{color:#fff;font-weight:normal;margin-right:0.5em}div.app>ul>li .platform{background-color:transparent;font-size:3em;font-weight:bold;position:absolute;right:5px;bottom:0;padding-left:0.2em;color:#fff}div.app>ul>li .changed-platform{color:#f77}div.app>ul>li .time{background-color:transparent;font-size:2.3em;position:absolute;right:5px;top:1px;padding-left:0.2em;color:#fff}div.app>ul>li .time.delayed{color:#f77;background-color:transparent}div.app>ul>li .time.a-bit-delayed{color:#d99;background-color:transparent}div.app>ul>li .time.on-time{color:#aea;background-color:transparent}div.app>ul>li .time .no-realtime{background-color:transparent;padding-right:1ex}div.app>ul>li .time .no-realtime i.material-icons{font-size:12px}div.app>ul>li .time .delay{font-size:1em;color:#f77;background-color:transparent;padding-right:1ex}div.app>ul>li .time .undelay{font-size:1em;color:#7f7;padding-right:1ex}div.app>ul>li .time .delaynorm{font-size:0.9em;color:#d99}div.app>ul>li .time .undelaynorm{font-size:0.9em;color:#9d9}div.app .trainsubtype{font-weight:normal;font-size:70%;position:relative;vertical-align:baseline;top:-0.6ex;left:-0.5ex}div.app .replacement{color:#afa}div.app .replaced{color:#faa}div.app .sbahn{font-weight:bold;border-radius:30px;padding:3px 6px 2px 6px;background-color:#151}div.app .bahn,div.app .fern,div.app .ext{font-weight:bold;border-radius:5px;padding:3px 5px 2px 5px}div.app .bahn{background-color:#333}div.app .fern{background-color:#511}div.app .ext{border:2px solid #333}div.app .tram,div.app .bus,div.app .ubahn{padding:3px 5px 2px 5px}div.app .tram{background-color:#411}div.app .bus{background-color:#515}div.app .ubahn{background-color:#071e62}div.app .moreinfo{font-size:2.1em;position:fixed;left:0;right:0;bottom:0em;z-index:5;overflow:auto;cursor:default;background-color:#101010}div.app .moreinfo .mheader,div.app .moreinfo .mfooter{max-width:50em;margin-left:auto;margin-right:auto}div.app .moreinfo .mheader{text-align:center;font-size:120%;padding-top:0.5em;padding-bottom:0.5em;padding-left:1em;padding-right:1em;border-bottom:0.1em dashed #cccccc}div.app .moreinfo .mfooter{padding-top:0.5em;padding-left:1em;padding-right:1em}div.app .moreinfo .dataline{font-size:120%;width:100%;display:flex;justify-content:space-between;margin-bottom:0.5em}div.app .moreinfo .dataline>div{width:33%}div.app .moreinfo .wagonorder-preview{font-size:110%;width:100%;text-align:center;margin-bottom:1em}div.app .moreinfo .wagonorder-preview a{color:#fff}div.app .moreinfo .wagonorder-preview .otherno{color:#bbb}div.app .moreinfo .wagonorder-preview .meta{color:#ddd}div.app .moreinfo .departure{text-align:right}div.app .moreinfo .platform{text-align:center}div.app .moreinfo .arrival{display:inline-block;text-align:right}div.app .moreinfo .loading{text-align:center;width:100%;color:#888888}div.app .moreinfo .minfo{color:#f77}div.app .moreinfo .timehidden{color:#bbb}div.app .moreinfo .undelay{color:#7f7}div.app .moreinfo .verbose{margin-bottom:1em}div.app .moreinfo .verbose .no-realtime{color:#f77}div.app .moreinfo .messages i.material-icons{font-size:14px}div.app .moreinfo .details{margin-top:1em}div.app .moreinfo .mroute .important-stop{color:#fff}div.app .moreinfo .mroute .generic-stop{color:#bbb}div.app .moreinfo .mroute .additional-stop{color:#7f7}div.app .moreinfo .mroute .cancelled-stop{color:#f77}div.app .moreinfo .mroute .past-stop{list-style-type:disc}div.app .moreinfo .mroute .future-stop{list-style-type:circle}div.app .moreinfo .mroute .time-early{color:#cfc}div.app .moreinfo .mroute .time-delayed{color:#f99}div.app .moreinfo .mroute .time-sched-only{color:#f99}div.app .moreinfo .mroute .time-sched-ontime{color:#cfc}div.app .moreinfo .mroute .annotation{color:#bbb;list-style-type:none;padding-left:3em}div.app .moreinfo .mroute .-sched:before{content:" "}div.app .moreinfo .mroute .time-sched:after{content:" "}div.app .moreinfo .mroute .time-sched-only:before{content:"("}div.app .moreinfo .mroute .time-sched-only:after{content:")"}div.app .moreinfo .mroute i.material-icons{font-size:14px}div.app .moreinfo .db-attr{margin-bottom:1em}div.app .moreinfo .db-attr span{margin-right:0.5em}div.app .collapsed-moreinfo{display:none}div.app .expanded-moreinfo{display:block}ul.ui-autocomplete{max-height:20em;overflow-x:hidden;overflow-y:auto}div.geolocation{text-align:center}div.candidatestatus{text-align:center;color:#999999}div.candidatelist a{display:block;text-decoration:none;font-size:1.4em;padding-top:0.3em;text-align:center;border-bottom:1px solid #999999}div.candidatelist a .distance:after{content:" km"}div.candidatelist a .distance{font-size:0.6em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.candidatelist a .traininfo{font-size:0.7em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.config{margin-top:2em;font-family:Sans-Serif;color:#bbb}div.config a{color:#99f;cursor:pointer;text-decoration:none}div.about{margin-top:1em;font-family:Sans-Serif;color:#bbb}div.about a{color:#99f;text-decoration:none}.notice{padding:15px;margin-bottom:20px;border:1px solid #bce8f1;border-radius:4px;color:#31708f;background-color:#d9edf7;margin-left:auto;margin-right:auto}.warning{padding:15px;margin-bottom:20px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.error{padding:15px;margin-bottom:20px;border:1px solid #ebccd1;border-radius:4px;color:#a94442;background-color:#f2dede;margin-left:auto;margin-right:auto}.error .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.container{max-width:60em;margin-left:auto;margin-right:auto}pre{margin-bottom:2em}span.optional,span.notes{color:#bbb}.moresettings-header{cursor:pointer}.moresettings-header-collapsed:before{content:"▹ "}.moresettings-header-expanded:before{content:"▿ "}.moresettings-collapsed{display:none}.moresettings-expanded{display:block}.developers-header{cursor:pointer}.developers-header-collapsed:before{content:"▹ "}.developers-header-expanded:before{content:"▿ "}.developers-collapsed{display:none}.developers-expanded{display:block}div.break{height:1em}div.field{margin-top:0.3em;margin-bottom:0.6em}.disabledbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #cccccc;box-shadow:none;padding:0.9ex;margin-right:1em}.smallbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #2e6da4;transition:background-color .3s;color:#fff;background-color:#337ab7;cursor:pointer;box-shadow:none;padding:0.9ex;margin-right:1em}.smallbutton .material-icons,.disabledbutton .material-icons{display:block;float:left;margin-right:0.5ex}.smallbutton img{display:block;float:left;margin-right:0.7ex;height:1.2em}input,select,.button{display:inline-block;width:60em;max-width:100%;min-height:1.8em;border-radius:4px;color:#fff;background-color:#101010;border:1px solid #444;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);font-size:90%;text-align:center;vertical-align:middle}input[type="text"]{width:59em;padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}select{min-height:2em}input[type="checkbox"]{width:1.5em;box-shadow:none}input[type="submit"],.button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none;padding-top:0.9ex;padding-bottom:0.9ex}.button{padding-top:1.1ex;padding-bottom:0}input[type="submit"]:active,input[type="submit"]:focus,input[type="submit"]:hover,.button:active,.button:focus,.button:hover,.smallbutton:active,.smallbutton:focus,.smallbutton:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="submit"]:active,.button:active{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.button-active{font-weight:bold}.button-light{color:#ddd;background-color:#101010;border-color:#444}.button-light:active,.button-light:focus,.button-light:hover{color:#ddd;background-color:#111;border-color:#333}div.backendlink{margin-top:1ex}div.notes{margin-top:2em}div.notes ul{margin-top:1em}div.app{max-width:60em;margin-left:auto;margin-right:auto}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed}nav{box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}nav{width:100%;overflow:hidden}nav a{color:#fff}nav .nav-wrapper{position:relative;height:100%}nav i,nav i.material-icons{display:block;font-size:24px}nav .brand-logo{position:absolute;display:inline-block;padding-left:0.5rem}nav ul{margin:0;padding-left:0;list-style-type:none}nav ul li{transition:background-color .3s;float:left;padding:0;list-style-type:none;background-color:#00838f}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}@media only screen and (max-width: 600px){div.app>ul>li{font-size:35%}div.navbar-fixed{height:56px}.moreinfo{top:56px}nav{height:56px;line-height:56px}nav .brand-logo{font-size:1.5rem}nav .nav-wrapper i{height:56px;line-height:56px}}@media only screen and (min-width: 600px){div.app>ul>li{font-size:40%}div.navbar-fixed{height:64px}.moreinfo{top:64px}nav{height:64px;line-height:64px}nav .brand-logo{font-size:2.1rem}nav .nav-wrapper i{height:64px;line-height:64px}}div.app .moreinfo{font-size:100%}
diff --git a/public/static/css/jquery-ui.min.css b/public/static/css/jquery-ui.min.css
new file mode 100644
index 0000000..a2cf45a
--- /dev/null
+++ b/public/static/css/jquery-ui.min.css
@@ -0,0 +1,7 @@
+/*! jQuery UI - v1.12.1 - 2019-05-04
+* http://jqueryui.com
+* Includes: core.css, autocomplete.css, menu.css, theme.css
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666} \ No newline at end of file
diff --git a/public/static/css/legacy-mobile.css b/public/static/css/legacy-mobile.css
new file mode 100644
index 0000000..0bf84d4
--- /dev/null
+++ b/public/static/css/legacy-mobile.css
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 Birte Kristina Friesel
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+div.app {
+ max-width: 60em;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+@media only screen and (max-width: 600px) {
+ div.app > ul > li {
+ font-size: 35%;
+ }
+}
+
+@media only screen and (min-width: 600px) {
+ div.app > ul > li {
+ font-size: 40%;
+ }
+}
+
+div.app .moreinfo {
+ font-size: 100%;
+}
diff --git a/public/static/css/legacy.css b/public/static/css/legacy.css
new file mode 100644
index 0000000..ac2eb79
--- /dev/null
+++ b/public/static/css/legacy.css
@@ -0,0 +1,788 @@
+/*
+ * Copyright (C) 2020 Birte Kristina Friesel
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+body {
+ margin: 0;
+}
+
+html {
+ font-family: Sans-Serif;
+}
+
+a {
+ color: #000099;
+ text-decoration: none;
+}
+
+p,
+div.about,
+div.input-field,
+div.notes {
+ max-width: 94%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+p {
+ text-align: justify;
+}
+
+div.content {
+ width: 100%;
+ margin: 0;
+}
+
+div.infoscreen {
+ border-width:1px 2px;
+ width:100%;
+ margin-bottom: 5em;
+}
+
+div.infoscreen > ul {
+ position:relative;
+ width:100%;
+
+ list-style-type:none;
+ margin:0;
+ padding:0;
+}
+
+div.infoscreen > ul > li {
+ min-height:7em;
+ display:block;
+ width:100%;
+ position:relative;
+}
+
+div.infoscreendark > ul > li {
+ border-bottom: 1px solid #999999;
+ background-color: #000000;
+}
+
+div.infoscreenlight > ul > li {
+ border-bottom: 1px solid #999999;
+ background-color: #ffffff;
+}
+
+div.infoscreen li .line {
+ font-size: 2.7em;
+ position:absolute;
+ bottom:5px;
+ left:2px;
+ max-width: 6em;
+ max-height: 3ex;
+ overflow: hidden;
+}
+
+div.infoscreen li .line .trainno {
+ font-weight: normal;
+}
+
+div.infoscreen li .line .trainno_sub {
+ font-weight: normal;
+ font-size: 0.6em;
+ text-align: center;
+ margin-top: -0.2em;
+}
+
+div.infoscreen li .sbahn .trainno_sub {
+ font-weight: normal;
+ font-size: 0.5em;
+ text-align: center;
+ margin-top: -0.25em;
+}
+
+div.infoscreen li .lineinfo {
+ color:#000000;
+ font-size: 2em;
+ position:absolute;
+ top:0px;
+ left:2px;
+}
+
+div.infoscreen .replacement {
+ color: #006600;
+}
+
+div.infoscreen .replaced {
+ color: #660000;
+}
+
+div.infoscreen li .sbahn {
+ font-weight:bold;
+ border-radius: 30px;
+ padding:3px 6px 2px 6px;
+}
+
+div.infoscreenlight li .sbahn {
+ background-color:#95d79f;
+}
+
+div.infoscreendark li .sbahn {
+ background-color:#115511;
+}
+
+div.infoscreen li .bahn,
+div.infoscreen li .fern,
+div.infoscreen li .ext {
+ font-weight:bold;
+ border-radius: 5px;
+ padding:3px 5px 2px 5px;
+}
+
+div.infoscreenlight li .bahn {
+ background-color: #eeeeee;
+}
+
+div.infoscreendark li .bahn {
+ background-color: #333333;
+}
+
+div.infoscreenlight li .fern {
+ background-color: #ffdddd;
+}
+
+div.infoscreendark li .fern {
+ background-color: #551111;
+}
+
+div.infoscreenlight li .ext {
+ background-color: #ffdddd;
+ border: 2px solid #ff6666;
+}
+
+div.infoscreendark li .ext {
+ background-color: #551111;
+ border: 2px solid #993333;
+}
+
+div.infoscreen li .route {
+ background-color: transparent;
+ font-size:2.1em;
+ position:absolute;
+ top:1px;
+ left:7.7em;
+ height: 1.2em;
+ width: 70%;
+ overflow: hidden;
+}
+
+div.infoscreenlight li .route {
+ color:#444444;
+}
+
+div.infoscreendark li .route {
+ color:#bbbbbb;
+}
+
+div.infoscreen li .info {
+ color:#ff0000;
+ background-color: transparent;
+ font-size:2.1em;
+ position:absolute;
+ top:1px;
+ left:7.7em;
+ height: 1.2em;
+ width: 70%;
+ overflow: hidden;
+}
+
+div.infoscreen li .moreinfo {
+ font-size:2.1em;
+ position:fixed;
+ top:0em;
+ left:0em;
+ right:0em;
+ bottom:0em;
+ z-index: 5;
+ overflow: auto;
+}
+
+div.infoscreenlight li .moreinfo {
+ background-color: #ffffff;
+}
+
+div.infoscreendark li .moreinfo {
+ background-color: #000000;
+}
+
+div.infoscreen li .collapsed-moreinfo {
+ display: none;
+}
+
+div.infoscreen li .expanded-moreinfo {
+ display: block;
+}
+
+div.infoscreen li .moreinfo .mheader {
+ text-align: center;
+ font-size: 120%;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ padding-left: 1em;
+ padding-right: 1em;
+ border-bottom: 0.1em dashed #cccccc;
+}
+
+div.infoscreen li .moreinfo .mfooter {
+ padding-top: 1em;
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+div.infoscreen li .moreinfo .platfominfo {
+ text-align: center;
+ padding-top: 1em;
+}
+
+div.infoscreen li .moreinfo .reason,
+div.infoscreen li .moreinfo .minfo {
+ color: #ff0000;
+}
+
+div.infoscreen li .moreinfo .timeinfo {
+ margin-bottom: 0.6em;
+}
+
+div.infoscreen li .moreinfo .mroute {
+ margin-bottom: 0.6em;
+}
+
+div.infoscreenlight li .moreinfo .mroute .separator {
+ color: #999999;
+}
+
+div.infoscreendark li .moreinfo .mroute .separator {
+ color: #999999;
+}
+
+div.infoscreenlight li .moreinfo .mroute .important-stop {
+ color: #000000;
+}
+
+div.infoscreendark li .moreinfo .mroute .important-stop {
+ color: #ffffff;
+}
+
+div.infoscreenlight li .moreinfo .mroute .generic-stop {
+ color: #555555;
+}
+
+div.infoscreendark li .moreinfo .mroute .generic-stop {
+ color: #999999;
+}
+
+div.infoscreenlight li .moreinfo .mroute .additional-stop {
+ color: #009900;
+}
+
+div.infoscreendark li .moreinfo .mroute .additional-stop {
+ color: #009900;
+}
+
+div.infoscreenlight li .moreinfo .mroute .cancelled-stop {
+ color: #cc0000;
+}
+
+div.infoscreendark li .moreinfo .mroute .cancelled-stop {
+ color: #cc0000;
+}
+
+div.infoscreen li .dest {
+ background-color: transparent;
+ font-size:4em;
+ position:absolute;
+ top:0.62em;
+ left:4em;
+ bottom:0px;
+ width: 70%;
+ overflow: hidden;
+}
+
+div.infoscreenlight li .dest {
+ color:#000000;
+}
+
+div.infoscreendark li .dest {
+ color:#ffffff;
+}
+
+div.infoscreenlight li.cancelled {
+ background-color: #ffe7d0;
+}
+
+div.infoscreendark li.cancelled {
+ background-color: #512f00;
+}
+
+div.infoscreen li .countdown {
+ background-color: transparent;
+ font-size: 3em;
+ position: absolute;
+ right: 5px;
+ bottom: 2px;
+ padding-left: 0.2em;
+}
+
+div.infoscreenlight li .countdown {
+ color: #000000;
+}
+
+div.infoscreendark li .countdown {
+ color: #ffffff;
+}
+
+div.infoscreen li .header {
+ color:#000000;
+ font-size:2em;
+ font-weight:bold;
+ padding-top:8px;
+ border-width-top:0;
+ display:block;
+ text-align:center;
+}
+
+div.infoscreen li .head {
+ border-bottom-width:0;
+}
+
+div.infoscreen li .countdown .delay {
+ font-size:1em;
+ color:#FF0000;
+ background-color: transparent;
+ padding-right:7px;
+}
+
+div.infoscreen li .countdown .undelay {
+ font-size:1em;
+ color:#006600;
+ padding-right:7px;
+}
+
+div.infoscreen li .countdown .delaynorm {
+ font-size:0.9em;
+ color:#BB3333;
+ padding-right:7px;
+}
+
+div.infoscreen li .countdown .undelaynorm {
+ font-size:0.9em;
+ color:#338833;
+ padding-right:7px;
+}
+
+div.infoscreen li .countdown .platform {
+ font-weight: bold;
+}
+
+div.infoscreen li .countdown .changed-platform {
+ color:#ff0000;
+}
+
+div.infoscreen li .time {
+ background-color: transparent;
+ font-size:2.3em;
+ position:absolute;
+ right:5px;
+ top:4px;
+ padding-left: 0.2em;
+}
+
+div.infoscreenlight li .time {
+ color:#000000;
+}
+
+div.infoscreendark li .time {
+ color:#ffffff;
+}
+
+div.infoscreen span.delayed {
+ color: #ff0000;
+ background-color: transparent;
+}
+
+/* ... */
+
+div.displaymulti {
+ border: 0.2em solid #000066;
+ width: 55em;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+div.displaymulti div.display {
+ background-color: #0000ff;
+ color: white;
+ font-family: Sans-Serif;
+ font-weight: bold;
+ position: relative;
+ margin-bottom: 0;
+ margin-top: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ width: 55em;
+ height: 1.4em;
+}
+
+div.displaymulti div.display div {
+ overflow: hidden;
+ position: absolute;
+ height: 100%;
+}
+
+div.displaymulti div.time {
+ left: 0;
+ width: 6%;
+ font-size: 95%;
+}
+
+div.displaymulti div.train {
+ left: 5%;
+ width: 9%;
+ background-color: white;
+ color: #0000ff;
+ font-size: 95%;
+}
+
+div.displaymulti div.via {
+ left: 15%;
+ width: 35%;
+}
+
+div.displaymulti div.via span {
+ margin-right: 0.4em;
+ font-size: 80%;
+}
+
+div.displaymulti div.destination {
+ left: 50%;
+ width: 25%;
+ font-size: 120%;
+}
+
+div.displaymulti div.platform {
+ left: 75%;
+ width: 5%;
+}
+
+div.displaymulti div.info {
+ left: 80%;
+ width: 20%;
+ background-color: white;
+ color: #0000ff;
+ font-size: 80%;
+ line-height: 150%;
+}
+
+div.displaymulti div.separator {
+ border-bottom: 0.1em solid #000066;
+}
+
+div.displaysingle div.display {
+ background-color: #0000ff;
+ color: white;
+ font-family: Sans-Serif;
+ font-weight: bold;
+ position: relative;
+ margin-left: 1em;
+ margin-top: 1em;
+ float: left;
+ width: 28em;
+ height: 4.5em;
+ border: 0.7em solid #000066;
+}
+
+div.displaysingle div.display div {
+ overflow: hidden;
+ position: absolute;
+}
+
+div.displaysingle div.no_data {
+ top: 0.5em;
+ left: 1em;
+ font-size: 1.7em;
+}
+
+div.displaysingle div.time {
+ top: 0em;
+ left: 0em;
+ font-size: 1.7em;
+}
+
+div.displaysingle div.train {
+ left: 0em;
+ top: 1.8em;
+}
+
+div.displaysingle div.via {
+ top: 1.5em;
+ left: 5.8em;
+ width: 17em;
+ height: 1em;
+}
+
+div.displaysingle div.via span {
+ margin-right: 0.4em;
+}
+
+div.displaysingle div.destination {
+ top: 1.6em;
+ left: 3.6em;
+ width: 12em;
+ font-size: 1.6em;
+ height: 1.2em;
+}
+
+div.displaysingle div.platform {
+ top: 0em;
+ right: 0em;
+ font-size: 3em;
+}
+
+div.displaysingle div.info {
+ top: 0em;
+ left: 5.8em;
+ width: 16.5em;
+ height: 1em;
+ background-color: white;
+ color: #0000ff;
+}
+
+ul.ui-autocomplete {
+ max-height: 20em;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+div.geolocation {
+ text-align: center;
+}
+
+div.candidatestatus {
+ text-align: center;
+ color: #999999;
+}
+
+div.candidatelist a {
+ display: block;
+ text-decoration: none;
+ font-size: 1.4em;
+ padding-top: 0.3em;
+ text-align: center;
+ border-bottom: 1px solid #999999;
+}
+
+div.candidatelist a .distance:after {
+ content: " km";
+}
+
+div.candidatelist a .distance {
+ font-size: 0.6em;
+ color: #999999;
+ padding-top: 0.2em;
+ padding-bottom: 0.3em;
+}
+
+div.about {
+ margin-top: 2em;
+ font-family: Sans-Serif;
+ color: #666666;
+}
+
+div.about a {
+ color: #000066;
+ text-decoration: none;
+}
+
+.notice {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid #bce8f1;
+ border-radius: 4px;
+ color: #31708f;
+ background-color: #d9edf7;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.warning {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid #faebcc;
+ border-radius: 4px;
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.error {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid #ebccd1;
+ border-radius: 4px;
+ color: #a94442;
+ background-color: #f2dede;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.error .errcode {
+ font-family: Monospace;
+ margin-top: 2em;
+ font-size: 100%;
+ color: #aaaaaa;
+}
+
+.container {
+ max-width: 60em;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+pre {
+ margin-bottom: 2em;
+}
+
+span.optional,
+span.notes {
+ color: #666666;
+}
+
+.moresettings-header {
+ cursor: pointer;
+}
+
+.moresettings-header-collapsed:before {
+ content: "▹ "
+}
+
+.moresettings-header-expanded:before {
+ content: "▿ "
+}
+
+.moresettings-collapsed {
+ display: none;
+}
+
+.moresettings-expanded {
+ display: block;
+}
+
+.developers-header {
+ cursor: pointer;
+}
+
+.developers-header-collapsed:before {
+ content: "▹ "
+}
+
+.developers-header-expanded:before {
+ content: "▿ "
+}
+
+.developers-collapsed {
+ display: none;
+}
+
+.developers-expanded {
+ display: block;
+}
+
+div.break {
+ height: 1em;
+}
+
+div.field {
+ margin-top: 0.3em;
+ margin-bottom: 0.6em;
+}
+
+input, select, .button {
+ display: inline-block;
+ width: 60em;
+ max-width: 100%;
+ min-height: 1.8em;
+ border-radius: 4px;
+ color: #000;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ font-size: 90%;
+ text-align: center;
+ vertical-align: middle;
+}
+
+input[type="text"] {
+ width: 59em;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ text-align: left;
+ box-sizing: border-box;
+}
+
+select {
+ min-height: 2em;
+}
+
+input[type="checkbox"] {
+ width: 1.5em;
+ box-shadow: none;
+}
+
+input[type="submit"], .button {
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #2e6da4;
+ cursor: pointer;
+ box-shadow: none;
+ padding-top: 0.9ex;
+ padding-bottom: 0.9ex;
+}
+
+.button {
+ padding-top: 1.1ex;
+ padding-bottom: 0;
+}
+
+input[type="submit"]:active,
+input[type="submit"]:focus,
+input[type="submit"]:hover,
+.button:active,
+.button:focus,
+.button:hover {
+ color: #fff;
+ background-color: #286090;
+ border-color: #204d74;
+}
+
+input[type="submit"]:active,
+.button.active {
+ box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
+}
+
+.button-light {
+ color: #333;
+ background-color: #fff;
+ border-color: #ccc;
+}
+
+.button-light:active,
+.button-light:focus,
+.button-light:hover
+{
+ color: #333;
+ background-color: #e6e6e6;
+ border-color: #adadad;
+}
+
+div.notes {
+ margin-top: 2em;
+}
+
+div.notes ul {
+ margin-top: 1em;
+}
diff --git a/public/static/css/light.min.css b/public/static/css/light.min.css
new file mode 100644
index 0000000..3128641
--- /dev/null
+++ b/public/static/css/light.min.css
@@ -0,0 +1 @@
+body{margin:0;color:#000;background-color:#fff}html{font-family:"Arimo", "Arial", Sans-Serif}a{color:#009;text-decoration:none}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}p,div.about,div.config,div.input-field,div.notes{max-width:94%;margin-left:auto;margin-right:auto}div.journey,div.nextstop{max-width:98%;margin-left:auto;margin-right:auto}p{text-align:justify}div.content{width:100%;margin:0}.copyright{margin-top:1em;color:#999;clear:both}.wagonorder{position:relative;width:100%;height:100ex}.wagonorder.exit-unknown .section{left:1em;width:2em}.wagonorder.exit-unknown .wagon{left:3em;min-width:6em}.wagonorder.exit-unknown .details{left:10em;right:0em}.wagonorder.exit-left .section{left:1em;width:2em;background-color:#ddd}.wagonorder.exit-left .wagon{left:3em;min-width:6em}.wagonorder.exit-left .details{left:10em;right:0em}.wagonorder.exit-right .section{right:1em;width:2em;background-color:#ddd}.wagonorder.exit-right .wagon{right:3em;min-width:6em}.wagonorder.exit-right .details{right:10em;left:0em;text-align:right}.wagonorder .section{position:absolute;text-align:center}.wagonorder .wagon{position:absolute;border:1px solid #999;padding-left:0.2em;padding-right:0.2em}.wagonorder .wagon .material-icons{color:#666}.wagonorder .wagon .direction{position:absolute;left:0.2em;bottom:0;right:0;text-align:center;color:#666}.wagonorder .wagon~.wagon{border-top:none}.wagonorder .firstclass{background-color:#ff9}.wagonorder .powercar{background-color:#ccc}.wagonorder .closed{background-color:#ddd}.wagonorder .nondestwagon{border-style:dashed}.wagonorder .details{position:absolute;padding-top:0.5ex}.wagonorder .details .type{display:inline-block;width:5em;color:#000}.wagonorder .details a.type{color:#009}.wagonorder .details .groupno{color:#000}.wagonorder .details .grouptype{color:#666}.wagonorder .details .grouptype:before{content:"("}.wagonorder .details .grouptype:after{content:")"}.wagonorder .details .uicunknown{color:#999}.wagonorder .details .uicexchange{margin-right:0.2em;color:#999}.wagonorder .details .uiccountry{margin-right:0.2em;color:#999}.wagonorder .details .uic5{margin-right:0.2em;color:#999}.wagonorder .details .uic56{color:#666;font-weight:bold}.wagonorder .details .uic78{margin-right:0.2em;color:#666;font-weight:bold}.wagonorder .details .uic78:before{content:"-"}.wagonorder .details .uictype{margin-right:0.2em;color:#666;font-weight:bold}.wagonorder .details .uicno{color:#666}.wagonorder .details .uiccheck{color:#999}.wagonorder .details .uiccheck:before{content:"-"}.singlewagon .sign-left{float:left;padding-left:5%}.singlewagon .sign-right{float:right;padding-right:5%}.singlewagon .sign-center{text-align:center}.singlewagon .platform{text-align:center;background-color:#ccc;font-weight:bold;padding-top:0.5em;padding-bottom:0.5em}.singlewagon img.wagonfile{width:100%;margin-top:0.2em;margin-bottom:0.2em}div.app{border-width:1px 2px;width:100%;margin-bottom:5em}div.app>ul{position:relative;width:100%;list-style-type:none;margin:0;padding:0}div.app>ul>li{min-height:7em;display:block;width:100%;position:relative;border-bottom:1px solid #999;background-color:#fff}div.app>ul>li.cancelled{background-color:#ffe7d0}div.app>ul>li.cancelled .time{color:#000 !important}div.app>ul>li.past{opacity:0.8;background-color:#ddd}div.app>ul>li>a{color:#000}div.app>ul>li .anchor{position:relative;top:-12em}div.app>ul>li .line{font-size:2.7em;position:absolute;bottom:5px;left:2px;max-width:6em;max-height:3ex;overflow:hidden}div.app>ul>li .line .trainno{font-weight:normal}div.app>ul>li .line .trainno_sub{font-weight:normal;font-size:0.6em;text-align:center;margin-top:-0.2em}div.app>ul>li .sbahn .trainno_sub{font-weight:normal;font-size:0.5em;text-align:center;margin-top:-0.25em}div.app>ul>li .lineinfo{color:#000;font-size:2em;position:absolute;top:0px;left:2px}div.app>ul>li .route,div.app>ul>li .info{background-color:transparent;font-size:2.1em;position:absolute;top:0;left:7.7em;right:7em;height:1.5em;overflow:hidden;white-space:nowrap}div.app>ul>li .route{color:#444}div.app>ul>li .info{color:red}div.app>ul>li .dest,div.app>ul>li .origin{background-color:transparent;font-size:4em;position:absolute;bottom:0;left:4em;width:70%;white-space:nowrap;overflow:hidden;color:#000}div.app>ul>li .dest{background-color:transparent;color:#000}div.app>ul>li .origin{background-color:transparent;color:#666}div.app>ul>li .origin:before{content:"von "}div.app>ul>li .load{color:#000;font-weight:normal;margin-right:0.5em}div.app>ul>li .platform{background-color:transparent;font-size:3em;font-weight:bold;position:absolute;right:5px;bottom:0;padding-left:0.2em;color:#000}div.app>ul>li .changed-platform{color:red}div.app>ul>li .time{background-color:transparent;font-size:2.3em;position:absolute;right:5px;top:1px;padding-left:0.2em;color:#000}div.app>ul>li .time.delayed{color:red;background-color:transparent}div.app>ul>li .time.a-bit-delayed{color:#b33;background-color:transparent}div.app>ul>li .time.on-time{color:#272;background-color:transparent}div.app>ul>li .time .no-realtime{background-color:transparent;padding-right:1ex}div.app>ul>li .time .no-realtime i.material-icons{font-size:12px}div.app>ul>li .time .delay{font-size:1em;color:red;background-color:transparent;padding-right:1ex}div.app>ul>li .time .undelay{font-size:1em;color:#060;padding-right:1ex}div.app>ul>li .time .delaynorm{font-size:0.9em;color:#b33}div.app>ul>li .time .undelaynorm{font-size:0.9em;color:#383}div.app .trainsubtype{font-weight:normal;font-size:70%;position:relative;vertical-align:baseline;top:-0.6ex;left:-0.5ex}div.app .replacement{color:#060}div.app .replaced{color:#600}div.app .sbahn{font-weight:bold;border-radius:30px;padding:3px 6px 2px 6px;background-color:#95d79f}div.app .bahn,div.app .fern,div.app .ext{font-weight:bold;border-radius:5px;padding:3px 5px 2px 5px}div.app .bahn{background-color:#eee}div.app .fern{background-color:#fdd}div.app .ext{border:2px solid #eee}div.app .tram,div.app .bus,div.app .ubahn{padding:3px 5px 2px 5px}div.app .tram{background-color:#fcc}div.app .bus{background-color:#eae}div.app .ubahn{background-color:#aac0ff}div.app .moreinfo{font-size:2.1em;position:fixed;left:0;right:0;bottom:0em;z-index:5;overflow:auto;cursor:default;background-color:#fff}div.app .moreinfo .mheader,div.app .moreinfo .mfooter{max-width:50em;margin-left:auto;margin-right:auto}div.app .moreinfo .mheader{text-align:center;font-size:120%;padding-top:0.5em;padding-bottom:0.5em;padding-left:1em;padding-right:1em;border-bottom:0.1em dashed #cccccc}div.app .moreinfo .mfooter{padding-top:0.5em;padding-left:1em;padding-right:1em}div.app .moreinfo .dataline{font-size:120%;width:100%;display:flex;justify-content:space-between;margin-bottom:0.5em}div.app .moreinfo .dataline>div{width:33%}div.app .moreinfo .wagonorder-preview{font-size:110%;width:100%;text-align:center;margin-bottom:1em}div.app .moreinfo .wagonorder-preview a{color:#000}div.app .moreinfo .wagonorder-preview .otherno{color:#666}div.app .moreinfo .wagonorder-preview .meta{color:#333}div.app .moreinfo .departure{text-align:right}div.app .moreinfo .platform{text-align:center}div.app .moreinfo .arrival{display:inline-block;text-align:right}div.app .moreinfo .loading{text-align:center;width:100%;color:#888888}div.app .moreinfo .minfo{color:red}div.app .moreinfo .timehidden{color:#666}div.app .moreinfo .undelay{color:#060}div.app .moreinfo .verbose{margin-bottom:1em}div.app .moreinfo .verbose .no-realtime{color:#c00}div.app .moreinfo .messages i.material-icons{font-size:14px}div.app .moreinfo .details{margin-top:1em}div.app .moreinfo .mroute .important-stop{color:#000}div.app .moreinfo .mroute .generic-stop{color:#666}div.app .moreinfo .mroute .additional-stop{color:#090}div.app .moreinfo .mroute .cancelled-stop{color:#c00}div.app .moreinfo .mroute .past-stop{list-style-type:disc}div.app .moreinfo .mroute .future-stop{list-style-type:circle}div.app .moreinfo .mroute .time-early{color:#070}div.app .moreinfo .mroute .time-delayed{color:#900}div.app .moreinfo .mroute .time-sched-only{color:#900}div.app .moreinfo .mroute .time-sched-ontime{color:#070}div.app .moreinfo .mroute .annotation{color:#666;list-style-type:none;padding-left:3em}div.app .moreinfo .mroute .-sched:before{content:" "}div.app .moreinfo .mroute .time-sched:after{content:" "}div.app .moreinfo .mroute .time-sched-only:before{content:"("}div.app .moreinfo .mroute .time-sched-only:after{content:")"}div.app .moreinfo .mroute i.material-icons{font-size:14px}div.app .moreinfo .db-attr{margin-bottom:1em}div.app .moreinfo .db-attr span{margin-right:0.5em}div.app .collapsed-moreinfo{display:none}div.app .expanded-moreinfo{display:block}ul.ui-autocomplete{max-height:20em;overflow-x:hidden;overflow-y:auto}div.geolocation{text-align:center}div.candidatestatus{text-align:center;color:#999999}div.candidatelist a{display:block;text-decoration:none;font-size:1.4em;padding-top:0.3em;text-align:center;border-bottom:1px solid #999999}div.candidatelist a .distance:after{content:" km"}div.candidatelist a .distance{font-size:0.6em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.candidatelist a .traininfo{font-size:0.7em;color:#999999;padding-top:0.2em;padding-bottom:0.3em}div.config{margin-top:2em;font-family:Sans-Serif;color:#666}div.config a{color:#009;cursor:pointer;text-decoration:none}div.about{margin-top:1em;font-family:Sans-Serif;color:#666}div.about a{color:#009;text-decoration:none}.notice{padding:15px;margin-bottom:20px;border:1px solid #bce8f1;border-radius:4px;color:#31708f;background-color:#d9edf7;margin-left:auto;margin-right:auto}.warning{padding:15px;margin-bottom:20px;border:1px solid #faebcc;border-radius:4px;color:#8a6d3b;background-color:#fcf8e3;margin-left:auto;margin-right:auto}.error{padding:15px;margin-bottom:20px;border:1px solid #ebccd1;border-radius:4px;color:#a94442;background-color:#f2dede;margin-left:auto;margin-right:auto}.error .errcode{font-family:Monospace;margin-top:2em;font-size:100%;color:#aaaaaa}.container{max-width:60em;margin-left:auto;margin-right:auto}pre{margin-bottom:2em}span.optional,span.notes{color:#666}.moresettings-header{cursor:pointer}.moresettings-header-collapsed:before{content:"▹ "}.moresettings-header-expanded:before{content:"▿ "}.moresettings-collapsed{display:none}.moresettings-expanded{display:block}.developers-header{cursor:pointer}.developers-header-collapsed:before{content:"▹ "}.developers-header-expanded:before{content:"▿ "}.developers-collapsed{display:none}.developers-expanded{display:block}div.break{height:1em}div.field{margin-top:0.3em;margin-bottom:0.6em}.disabledbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #cccccc;box-shadow:none;padding:0.9ex;margin-right:1em}.smallbutton{display:inline-block;vertical-align:baseline;border-radius:4px;border:1px solid #2e6da4;transition:background-color .3s;color:#fff;background-color:#337ab7;cursor:pointer;box-shadow:none;padding:0.9ex;margin-right:1em}.smallbutton .material-icons,.disabledbutton .material-icons{display:block;float:left;margin-right:0.5ex}.smallbutton img{display:block;float:left;margin-right:0.7ex;height:1.2em}input,select,.button{display:inline-block;width:60em;max-width:100%;min-height:1.8em;border-radius:4px;color:#000;background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);font-size:90%;text-align:center;vertical-align:middle}input[type="text"]{width:59em;padding-left:0.5em;padding-right:0.5em;text-align:left;box-sizing:border-box}select{min-height:2em}input[type="checkbox"]{width:1.5em;box-shadow:none}input[type="submit"],.button{transition:background-color .3s;color:#fff;background-color:#337ab7;border-color:#2e6da4;cursor:pointer;box-shadow:none;padding-top:0.9ex;padding-bottom:0.9ex}.button{padding-top:1.1ex;padding-bottom:0}input[type="submit"]:active,input[type="submit"]:focus,input[type="submit"]:hover,.button:active,.button:focus,.button:hover,.smallbutton:active,.smallbutton:focus,.smallbutton:hover{color:#fff;background-color:#286090;border-color:#204d74}input[type="submit"]:active,.button:active{box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.button-active{font-weight:bold}.button-light{color:#333;background-color:#fff;border-color:#ccc}.button-light:active,.button-light:focus,.button-light:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}div.backendlink{margin-top:1ex}div.notes{margin-top:2em}div.notes ul{margin-top:1em}div.app{max-width:60em;margin-left:auto;margin-right:auto}.navbar-fixed{position:relative;z-index:997}.navbar-fixed nav{position:fixed}nav{box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}nav{width:100%;overflow:hidden}nav a{color:#fff}nav .nav-wrapper{position:relative;height:100%}nav i,nav i.material-icons{display:block;font-size:24px}nav .brand-logo{position:absolute;display:inline-block;padding-left:0.5rem}nav ul{margin:0;padding-left:0;list-style-type:none}nav ul li{transition:background-color .3s;float:left;padding:0;list-style-type:none;background-color:#00838f}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}@media only screen and (max-width: 600px){div.app>ul>li{font-size:35%}div.navbar-fixed{height:56px}.moreinfo{top:56px}nav{height:56px;line-height:56px}nav .brand-logo{font-size:1.5rem}nav .nav-wrapper i{height:56px;line-height:56px}}@media only screen and (min-width: 600px){div.app>ul>li{font-size:40%}div.navbar-fixed{height:64px}.moreinfo{top:64px}nav{height:64px;line-height:64px}nav .brand-logo{font-size:2.1rem}nav .nav-wrapper i{height:64px;line-height:64px}}div.app .moreinfo{font-size:100%}
diff --git a/public/static/css/material-icons.css b/public/static/css/material-icons.css
new file mode 100644
index 0000000..662e6b7
--- /dev/null
+++ b/public/static/css/material-icons.css
@@ -0,0 +1,36 @@
+@font-face {
+ font-family: 'Material Icons';
+ font-style: normal;
+ font-weight: 400;
+ src: url(/static/v110/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
+ src: local('Material Icons'),
+ local('MaterialIcons-Regular'),
+ url(/static/v110/fonts/MaterialIcons-Regular.woff2) format('woff2'),
+ url(/static/v110/fonts/MaterialIcons-Regular.woff) format('woff'),
+ url(/static/v110/fonts/MaterialIcons-Regular.ttf) format('truetype');
+}
+
+.material-icons {
+ font-family: 'Material Icons';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 20px; /* Preferred icon size */
+ display: inline-block;
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+
+ /* Support for all WebKit browsers. */
+ -webkit-font-smoothing: antialiased;
+ /* Support for Safari and Chrome. */
+ text-rendering: optimizeLegibility;
+
+ /* Support for Firefox. */
+ -moz-osx-font-smoothing: grayscale;
+
+ /* Support for IE. */
+ font-feature-settings: 'liga';
+}
diff --git a/public/static/fonts/MaterialIcons-Regular.eot b/public/static/fonts/MaterialIcons-Regular.eot
new file mode 100644
index 0000000..70508eb
--- /dev/null
+++ b/public/static/fonts/MaterialIcons-Regular.eot
Binary files differ
diff --git a/public/static/fonts/MaterialIcons-Regular.ttf b/public/static/fonts/MaterialIcons-Regular.ttf
new file mode 100644
index 0000000..7015564
--- /dev/null
+++ b/public/static/fonts/MaterialIcons-Regular.ttf
Binary files differ
diff --git a/public/static/fonts/MaterialIcons-Regular.woff b/public/static/fonts/MaterialIcons-Regular.woff
new file mode 100644
index 0000000..b648a3e
--- /dev/null
+++ b/public/static/fonts/MaterialIcons-Regular.woff
Binary files differ
diff --git a/public/static/fonts/MaterialIcons-Regular.woff2 b/public/static/fonts/MaterialIcons-Regular.woff2
new file mode 100644
index 0000000..9fa2112
--- /dev/null
+++ b/public/static/fonts/MaterialIcons-Regular.woff2
Binary files differ
diff --git a/public/static/icons/bahn-expert.svg b/public/static/icons/bahn-expert.svg
new file mode 100644
index 0000000..4cee072
--- /dev/null
+++ b/public/static/icons/bahn-expert.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="910.683" height="911.613" viewBox="0 0 240.952 241.198">
+ <path
+ d="M156.283 14.31l-76.155.03a6.102 6.102 0 0 0-.247 12.197c7.448.304 14.872 2.649 21.144 6.675 3.91 2.511 7.182 5.608 9.07 9.101.5.927.916 1.91 1.243 2.925-8.798.583-18.387 1.932-27.009 3.806-5.011 1.088-10.013 2.373-14.69 4.477-4.679 2.103-9.011 5.062-12.391 8.92-3.38 3.858-5.735 8.575-6.962 13.555-1.228 4.98-1.322 10.16-1.322 15.29v72.291c0 4.87.066 9.793 1.25 14.517 1.183 4.724 3.513 9.198 6.957 12.642 3.444 3.444 7.918 5.774 12.642 6.957 3.2.802 6.492 1.086 9.797 1.19l-21.18 31.596h16.19l8.39-12.515 75.786.067 8.284 12.355h16.189l-21.134-31.528c3.038-.124 6.059-.427 9.003-1.164 4.724-1.184 9.199-3.514 12.642-6.957 3.444-3.444 5.774-7.918 6.958-12.642 1.183-4.724 1.25-9.647 1.25-14.517V91.286c0-5.117-.096-10.285-1.328-15.25-1.232-4.967-3.594-9.668-6.98-13.504-3.385-3.837-7.72-6.77-12.393-8.853-4.674-2.083-9.666-3.351-14.665-4.44-9.599-2.092-18.512-3.595-28.316-4.119a20.03 20.03 0 0 1 1.782-3.754c1.785-2.919 4.444-5.549 7.575-7.78a39.466 39.466 0 0 1 19.172-7.098 6.102 6.102 0 0 0 5.522-6.351c-.148-3.25-2.82-5.797-6.074-5.826zm-29.096 12.215c-2.77 2.395-5.38 5.062-7.415 8.356-2.037-3.255-4.554-6.019-7.34-8.35zm-20.641 47.72c8.312 0 14.465 1.246 18.458 3.74 7.254-2.494 13.895-3.74 19.926-3.74 8.639 0 15.118 1.473 19.437 4.42 4.32 2.946 6.479 8.197 6.479 15.751v39.324h-22.249V99.403c0-2.947-.855-5.062-2.567-6.346-1.71-1.36-4.237-2.04-7.579-2.04-1.467 0-2.77.188-3.912.566-1.14.378-1.833.794-2.078 1.247v40.91h-22.248V99.403c0-2.947-.856-5.062-2.567-6.346-1.712-1.36-4.238-2.04-7.58-2.04-1.466 0-2.77.188-3.911.566-1.141.378-1.834.794-2.078 1.247v40.91H71.829V81.044c2.852-1.435 8.027-2.908 15.524-4.42 7.498-1.586 13.896-2.379 19.193-2.379zM83.483 152.82c7.27 0 13.165 5.895 13.165 13.166 0 7.27-5.894 13.165-13.165 13.165-7.271 0-13.166-5.894-13.166-13.165 0-7.271 5.895-13.166 13.166-13.166zm74.655 0c7.272 0 13.166 5.895 13.166 13.166 0 7.27-5.895 13.165-13.166 13.165-7.27 0-13.165-5.894-13.165-13.165 0-7.271 5.894-13.166 13.165-13.166zm-62.375 46.122H146l5.975 8.913-62.15-.054z"
+ fill="#FFF" />
+</svg>
diff --git a/public/static/icons/dbf.svg b/public/static/icons/dbf.svg
new file mode 100644
index 0000000..88b3754
--- /dev/null
+++ b/public/static/icons/dbf.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="24"
+ height="24"
+ viewBox="0 0 24 24"
+ version="1.1"
+ id="svg18"
+ sodipodi:docname="baseline-train-24px.svg"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+ inkscape:export-filename="/tmp/baseline-train-24px.svg.png"
+ inkscape:export-xdpi="8192"
+ inkscape:export-ydpi="8192">
+ <metadata
+ id="metadata24">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs22" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1918"
+ inkscape:window-height="2131"
+ id="namedview20"
+ showgrid="false"
+ inkscape:zoom="39.333333"
+ inkscape:cx="9.6458586"
+ inkscape:cy="14.496166"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg18" />
+ <g
+ id="g4573">
+ <rect
+ y="6.2033901"
+ x="2.8220339"
+ height="12.737288"
+ width="13.398306"
+ id="rect4564"
+ style="fill:#ffffff;fill-opacity:1;stroke-width:1.88601935" />
+ <g
+ id="g4562">
+ <path
+ d="m 9.5847458,3.6779661 c -4,0 -8,0.5 -8,4 v 9.4999999 c 0,1.93 1.57,3.5 3.5,3.5 l -1.5,1.5 v 0.5 h 2.23 l 2,-2 h 3.7700002 l 2,2 h 2 v -0.5 l -1.5,-1.5 c 1.93,0 3.5,-1.57 3.5,-3.5 V 7.6779661 c 0,-3.5 -3.58,-4 -8.0000002,-4 z m -4.5,14.9999999 c -0.83,0 -1.5,-0.67 -1.5,-1.5 0,-0.83 0.67,-1.5 1.5,-1.5 0.83,0 1.5,0.67 1.5,1.5 0,0.83 -0.67,1.5 -1.5,1.5 z m 3.5,-7 h -5 V 7.6779661 h 5 z m 2.0000002,0 V 7.6779661 h 5 v 3.9999999 z m 3.5,7 c -0.83,0 -1.5,-0.67 -1.5,-1.5 0,-0.83 0.67,-1.5 1.5,-1.5 0.83,0 1.5,0.67 1.5,1.5 0,0.83 -0.67,1.5 -1.5,1.5 z"
+ id="path14"
+ inkscape:connector-curvature="0" />
+ <path
+ fill="none"
+ d="M0 0h24v24H0V0z"
+ id="path16" />
+ <g
+ id="g4555"
+ transform="translate(0.94067792,10.194916)">
+ <ellipse
+ style="fill:#ffffff;fill-opacity:1"
+ ry="6.1906781"
+ rx="6.2161016"
+ cy="-2.9364405"
+ cx="15.343221"
+ id="path43" />
+ <path
+ sodipodi:nodetypes="scccsssssssccccccc"
+ inkscape:connector-curvature="0"
+ id="path4"
+ d="m 15.021035,-9.6313779 c -2.4,0 -4.52,1.2100001 -5.7800001,3.0500001 0.01,-0.01 0.01,-0.02 0.02,-0.03 -1.6039009,2.628948 -2.1637288,10.9303693 6.7600001,10.9000004 3.39,-0.49 6,-3.39 6,-6.9200004 0,-3.87 -3.13,-7.0000001 -7,-7.0000001 z m 0,12.0000005 c -2.76,0 -5,-2.2400004 -5,-5.0000004 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 0,2.76 -2.24,5.0000004 -5,5.0000004 z m 0.5,-9.0000004 h -1.5 v 5 l 3.62,2.16 0.75,-1.23 -2.87,-1.68 z" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/public/static/icons/icon-120x120.png b/public/static/icons/icon-120x120.png
new file mode 100644
index 0000000..630e699
--- /dev/null
+++ b/public/static/icons/icon-120x120.png
Binary files differ
diff --git a/public/static/icons/icon-128x128.png b/public/static/icons/icon-128x128.png
new file mode 100644
index 0000000..2b1576a
--- /dev/null
+++ b/public/static/icons/icon-128x128.png
Binary files differ
diff --git a/public/static/icons/icon-144x144.png b/public/static/icons/icon-144x144.png
new file mode 100644
index 0000000..e6d9184
--- /dev/null
+++ b/public/static/icons/icon-144x144.png
Binary files differ
diff --git a/public/static/icons/icon-152x152.png b/public/static/icons/icon-152x152.png
new file mode 100644
index 0000000..b88b2e6
--- /dev/null
+++ b/public/static/icons/icon-152x152.png
Binary files differ
diff --git a/public/static/icons/icon-167x167.png b/public/static/icons/icon-167x167.png
new file mode 100644
index 0000000..0ca3ee6
--- /dev/null
+++ b/public/static/icons/icon-167x167.png
Binary files differ
diff --git a/public/static/icons/icon-16x16.png b/public/static/icons/icon-16x16.png
new file mode 100644
index 0000000..30c3b1d
--- /dev/null
+++ b/public/static/icons/icon-16x16.png
Binary files differ
diff --git a/public/static/icons/icon-180x180.png b/public/static/icons/icon-180x180.png
new file mode 100644
index 0000000..4409ea5
--- /dev/null
+++ b/public/static/icons/icon-180x180.png
Binary files differ
diff --git a/public/static/icons/icon-192x192.png b/public/static/icons/icon-192x192.png
new file mode 100644
index 0000000..f1b8403
--- /dev/null
+++ b/public/static/icons/icon-192x192.png
Binary files differ
diff --git a/public/static/icons/icon-256x256.png b/public/static/icons/icon-256x256.png
new file mode 100644
index 0000000..b2a665d
--- /dev/null
+++ b/public/static/icons/icon-256x256.png
Binary files differ
diff --git a/public/static/icons/icon-32x32.png b/public/static/icons/icon-32x32.png
new file mode 100644
index 0000000..24b3451
--- /dev/null
+++ b/public/static/icons/icon-32x32.png
Binary files differ
diff --git a/public/static/icons/icon-512x512.png b/public/static/icons/icon-512x512.png
new file mode 100644
index 0000000..ddc78b9
--- /dev/null
+++ b/public/static/icons/icon-512x512.png
Binary files differ
diff --git a/public/static/icons/icon-96x96.png b/public/static/icons/icon-96x96.png
new file mode 100644
index 0000000..88379ed
--- /dev/null
+++ b/public/static/icons/icon-96x96.png
Binary files differ
diff --git a/public/static/js/collapse.js b/public/static/js/collapse.js
new file mode 100644
index 0000000..e861169
--- /dev/null
+++ b/public/static/js/collapse.js
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2020-2023 Birte Kristina Friesel
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+function setLang(lang) {
+ document.cookie = 'lang=' + lang + ';SameSite=None;Secure';
+ location.reload();
+}
+
+function setTheme(theme) {
+ localStorage.setItem('theme', theme);
+ if (!otherTheme.hasOwnProperty(theme)) {
+ theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ }
+ addStyleSheet(theme, 'theme');
+}
+
+function reload_app() {
+ // TODO use a variable instead of window.location.href, as
+ // window.location.href may be /z/...
+ // Until then, we guard it by only reloading whin moreinfo is not expanded.
+ if ($('.expanded-moreinfo').length == 0) {
+ $.get(window.location.href, {ajax: 1}, function(data) {
+ // TODO check expanded-moreinfo again here (until the issue itself has been resolved)
+ $('div.app > ul').html(data);
+ dbf_reg_handlers();
+ setTimeout(reload_app, 60000);
+ }).fail(function() {
+ setTimeout(reload_app, 10000);
+ });
+ } else {
+ setTimeout(reload_app, 30000);
+ }
+}
+
+function dbf_show_moreinfo(trainElem, keep_old) {
+ const routeprev = trainElem.data('routeprev').split('|');
+ const routenext = trainElem.data('routenext').split('|');
+ const moreinfo = trainElem.data('moreinfo').split('|');
+ $('.moreinfo').each(function() {
+ const infoElem = $(this);
+ if (!keep_old) {
+ $('.moreinfo .train-line').removeClass('sbahn fern ext ubahn bus tram').addClass(trainElem.data('linetype'));
+ $('.moreinfo .train-line').text(trainElem.data('line'));
+ $('.moreinfo .train-no').text(trainElem.data('no'));
+ $('.moreinfo .train-origin').text(trainElem.data('from'));
+ $('.moreinfo .train-dest').text(trainElem.data('to'));
+ $('.moreinfo .minfo').text('');
+ $('.moreinfo .mfooter').html('');
+ $('.moreinfo .verbose').html('');
+ $('.moreinfo .mroute').html('');
+ $('.moreinfo ul').html('');
+ var dataline = '';
+ if (trainElem.data('arrival') != '') {
+ dataline += '<div><div class="arrival">An: ' + trainElem.data('arrival') + '</div></div>';
+ } else {
+ dataline += '<div><div class="arrival"></div></div>';
+ }
+ if (trainElem.data('platform') != '') {
+ dataline += '<div><div class="platform">Gleis ' + trainElem.data('platform') + '</div></div>';
+ } else {
+ dataline += '<div><div class="platform"></div></div>';
+ }
+ if (trainElem.data('departure') != '') {
+ dataline += '<div><div class="departure">Ab: ' + trainElem.data('departure') + '</div></div>';
+ } else {
+ dataline += '<div><div class="departure"></div></div>';
+ }
+ $('.moreinfo .mfooter').append('<div class="dataline">' + dataline + '</div>');
+ if ($('.moreinfo .loading').length == 0) {
+ $('.moreinfo .mfooter').append('<div class="loading">Lade Daten, bitte warten...</div>');
+ }
+ if (trainElem.data('moreinfo') != '') {
+ var ibuf = '';
+ for (var key in moreinfo) {
+ ibuf += '<li>' + moreinfo[key] + '</li>';
+ }
+ $('.moreinfo .mfooter').append('Meldungen: <ul>' + ibuf + '</ul>');
+ }
+ var routebuf = '';
+ if (trainElem.data('routeprev') != '') {
+ for (var key in routeprev) {
+ routebuf += '<li>' + routeprev[key] + '</li>';
+ }
+ }
+ routebuf += '<li><strong>' + document.title + '</strong></li>';
+ if (trainElem.data('routenext') != '') {
+ for (var key in routenext) {
+ routebuf += '<li>' + routenext[key] + '</li>';
+ }
+ }
+ $('.moreinfo .mfooter').append('Fahrtverlauf: <ul class="mroute">' + routebuf + '</ul>');
+ }
+ $.get(window.location.href, {train: trainElem.data('train'), jid: trainElem.data('jid'), ajax: 1}, function(data) {
+ $('.moreinfo').html(data);
+ }).fail(function() {
+ $('.moreinfo .mfooter').append('Keine weiteren Details verfügbar');
+ $('.moreinfo .loading').remove();
+ });
+ infoElem.removeClass('collapsed-moreinfo');
+ infoElem.addClass('expanded-moreinfo');
+ });
+}
+
+function dbf_reg_handlers() {
+ $('div.app > ul > li').click(function(event) {
+ const trainElem = $(this);
+ const station = $('div.app').data('station');
+ const param = new URLSearchParams(window.location.search);
+ event.preventDefault();
+ var suffix = '?';
+ if (param.get('detailed')) {
+ suffix += '&detailed=1';
+ }
+ if (param.get('dbris') && param.get('dbris') != '0') {
+ suffix += '&dbris=' + param.get('dbris') + '&highlight=' + trainElem.data('station');
+ }
+ if (param.get('efa') && param.get('efa') != '0') {
+ suffix += '&efa=' + param.get('efa') + '&highlight=' + trainElem.data('station');
+ }
+ if (param.get('hafas') && param.get('hafas') != '0') {
+ suffix += '&hafas=' + param.get('hafas') + '&highlight=' + trainElem.data('station');
+ }
+ if (param.get('past')) {
+ suffix += '&past=1';
+ }
+ if (param.get('rt') || param.get('show_realtime')) {
+ suffix += '&rt=1';
+ }
+ if (param.get('hafas') && param.get('hafas') != '0') {
+ history.pushState({'page':'traindetail','jid':trainElem.data('jid')}, 'test', '/z/' + trainElem.data('jid') + suffix);
+ } else if (param.get('efa') && param.get('efa') != '0') {
+ history.pushState({'page':'traindetail','jid':trainElem.data('jid')}, 'test', '/z/' + trainElem.data('jid') + suffix);
+ } else if (param.get('dbris') && param.get('dbris') != '0') {
+ history.pushState({'page':'traindetail','jid':trainElem.data('jid')}, 'test', '/z/' + trainElem.data('jid') + suffix);
+ } else {
+ history.pushState({'page':'traindetail','station':station,'train':trainElem.data('no')}, 'test', '/z/' + trainElem.data('train') + '/' + trainElem.data('station') + suffix);
+ }
+ dbf_show_moreinfo(trainElem, false);
+ });
+ const trainid = $(location).attr('hash').substr(1);
+ if (trainid) {
+ var found = false;
+ $('div.app > ul > li').each(function(index) {
+ if (found) {
+ return;
+ }
+ $(this).find('.anchor').each(function() {
+ if ($(this).attr('id') == trainid) {
+ found = true;
+ }
+ });
+ });
+ if (found) {
+ found = false;
+ $('div.app > ul > li').each(function(index) {
+ if (found) {
+ return;
+ }
+ $(this).find('.anchor').each(function() {
+ if ($(this).attr('id') == trainid) {
+ found = true;
+ }
+ });
+ if (found) {
+ $(this).addClass('selected');
+ } else {
+ $(this).addClass('past');
+ }
+ });
+ }
+ }
+}
+
+$(function() {
+ $('.moresettings-header').each(function() {
+ $(this).click(function() {
+ var moresettings = $('.moresettings');
+ if ($(this).hasClass('moresettings-header-collapsed')) {
+ $(this).removeClass('moresettings-header-collapsed');
+ $(this).addClass('moresettings-header-expanded');
+ moresettings.removeClass('moresettings-collapsed');
+ moresettings.addClass('moresettings-expanded');
+ }
+ else {
+ $(this).removeClass('moresettings-header-expanded');
+ $(this).addClass('moresettings-header-collapsed');
+ moresettings.removeClass('moresettings-expanded');
+ moresettings.addClass('moresettings-collapsed');
+ }
+ });
+ });
+ $('.developers-header').each(function() {
+ $(this).click(function() {
+ var developers = $('.developers');
+ if ($(this).hasClass('developers-header-collapsed')) {
+ $(this).removeClass('developers-header-collapsed');
+ $(this).addClass('developers-header-expanded');
+ developers.removeClass('developers-collapsed');
+ developers.addClass('developers-expanded');
+ }
+ else {
+ $(this).removeClass('developers-header-expanded');
+ $(this).addClass('developers-header-collapsed');
+ developers.removeClass('developers-expanded');
+ developers.addClass('developers-collapsed');
+ }
+ });
+ });
+ dbf_reg_handlers();
+ if ($('.content .app').length) {
+ setTimeout(reload_app, 30000);
+ history.replaceState({'page':'station'}, document.title, '');
+ }
+ window.onpopstate = function(event) {
+ if (event.state != null) {
+ if ((event.state['page'] == 'station')) {
+ $('.moreinfo').each(function() {
+ $(this).removeClass('expanded-moreinfo');
+ $(this).addClass('collapsed-moreinfo');
+ });
+ if (!$('div.app > ul').length) {
+ $('div.app').append('<ul></ul>');
+ reload_app();
+ }
+ } else if ((event.state['page'] == 'traindetail')) {
+ var success = false;
+ $('div.app > ul > li').each(function() {
+ const trainElem = $(this);
+ if (trainElem.data('no') == event.state['train']) {
+ dbf_show_moreinfo(trainElem, true);
+ success = true;
+ return;
+ }
+ });
+ if (!success) {
+ $('.moreinfo').each(function() {
+ $(this).removeClass('collapsed-moreinfo');
+ $(this).addClass('expanded-moreinfo');
+ });
+ $('.moreinfo .mfooter').append('Der Zug ist abgefahren (Zug nicht gefunden)');
+ }
+ }
+ } else {
+ console.log("unhandled popstate! " + document.location);
+ }
+ };
+});
diff --git a/public/static/js/dbf.min.js b/public/static/js/dbf.min.js
new file mode 100644
index 0000000..f977bbd
--- /dev/null
+++ b/public/static/js/dbf.min.js
@@ -0,0 +1 @@
+function setLang(e){document.cookie="lang="+e+";SameSite=None;Secure",location.reload()}function setTheme(e){localStorage.setItem("theme",e),otherTheme.hasOwnProperty(e)||(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),addStyleSheet(e,"theme")}function reload_app(){0==$(".expanded-moreinfo").length?$.get(window.location.href,{ajax:1},function(e){$("div.app > ul").html(e),dbf_reg_handlers(),setTimeout(reload_app,6e4)}).fail(function(){setTimeout(reload_app,1e4)}):setTimeout(reload_app,3e4)}function dbf_show_moreinfo(d,s){const n=d.data("routeprev").split("|"),r=d.data("routenext").split("|"),l=d.data("moreinfo").split("|");$(".moreinfo").each(function(){var e=$(this);if(!s){$(".moreinfo .train-line").removeClass("sbahn fern ext ubahn bus tram").addClass(d.data("linetype")),$(".moreinfo .train-line").text(d.data("line")),$(".moreinfo .train-no").text(d.data("no")),$(".moreinfo .train-origin").text(d.data("from")),$(".moreinfo .train-dest").text(d.data("to")),$(".moreinfo .minfo").text(""),$(".moreinfo .mfooter").html(""),$(".moreinfo .verbose").html(""),$(".moreinfo .mroute").html(""),$(".moreinfo ul").html("");var a="";if(""!=d.data("arrival")?a+='<div><div class="arrival">An: '+d.data("arrival")+"</div></div>":a+='<div><div class="arrival"></div></div>',""!=d.data("platform")?a+='<div><div class="platform">Gleis '+d.data("platform")+"</div></div>":a+='<div><div class="platform"></div></div>',""!=d.data("departure")?a+='<div><div class="departure">Ab: '+d.data("departure")+"</div></div>":a+='<div><div class="departure"></div></div>',$(".moreinfo .mfooter").append('<div class="dataline">'+a+"</div>"),0==$(".moreinfo .loading").length&&$(".moreinfo .mfooter").append('<div class="loading">Lade Daten, bitte warten...</div>'),""!=d.data("moreinfo")){var t="";for(o in l)t+="<li>"+l[o]+"</li>";$(".moreinfo .mfooter").append("Meldungen: <ul>"+t+"</ul>")}var i="";if(""!=d.data("routeprev"))for(var o in n)i+="<li>"+n[o]+"</li>";if(i+="<li><strong>"+document.title+"</strong></li>",""!=d.data("routenext"))for(var o in r)i+="<li>"+r[o]+"</li>";$(".moreinfo .mfooter").append('Fahrtverlauf: <ul class="mroute">'+i+"</ul>")}$.get(window.location.href,{train:d.data("train"),jid:d.data("jid"),ajax:1},function(e){$(".moreinfo").html(e)}).fail(function(){$(".moreinfo .mfooter").append("Keine weiteren Details verfügbar"),$(".moreinfo .loading").remove()}),e.removeClass("collapsed-moreinfo"),e.addClass("expanded-moreinfo")})}function dbf_reg_handlers(){$("div.app > ul > li").click(function(e){var a=$(this),t=$("div.app").data("station"),i=new URLSearchParams(window.location.search),e=(e.preventDefault(),"?");i.get("detailed")&&(e+="&detailed=1"),i.get("dbris")&&"0"!=i.get("dbris")&&(e+="&dbris="+i.get("dbris")+"&highlight="+a.data("station")),i.get("efa")&&"0"!=i.get("efa")&&(e+="&efa="+i.get("efa")+"&highlight="+a.data("station")),i.get("hafas")&&"0"!=i.get("hafas")&&(e+="&hafas="+i.get("hafas")+"&highlight="+a.data("station")),i.get("past")&&(e+="&past=1"),(i.get("rt")||i.get("show_realtime"))&&(e+="&rt=1"),i.get("hafas")&&"0"!=i.get("hafas")||i.get("efa")&&"0"!=i.get("efa")||i.get("dbris")&&"0"!=i.get("dbris")?history.pushState({page:"traindetail",jid:a.data("jid")},"test","/z/"+a.data("jid")+e):history.pushState({page:"traindetail",station:t,train:a.data("no")},"test","/z/"+a.data("train")+"/"+a.data("station")+e),dbf_show_moreinfo(a,!1)});const a=$(location).attr("hash").substr(1);var t;a&&(t=!1,$("div.app > ul > li").each(function(e){t||$(this).find(".anchor").each(function(){$(this).attr("id")==a&&(t=!0)})}),t)&&(t=!1,$("div.app > ul > li").each(function(e){t||($(this).find(".anchor").each(function(){$(this).attr("id")==a&&(t=!0)}),t?$(this).addClass("selected"):$(this).addClass("past"))}))}$(function(){$(".moresettings-header").each(function(){$(this).click(function(){var e=$(".moresettings");$(this).hasClass("moresettings-header-collapsed")?($(this).removeClass("moresettings-header-collapsed"),$(this).addClass("moresettings-header-expanded"),e.removeClass("moresettings-collapsed"),e.addClass("moresettings-expanded")):($(this).removeClass("moresettings-header-expanded"),$(this).addClass("moresettings-header-collapsed"),e.removeClass("moresettings-expanded"),e.addClass("moresettings-collapsed"))})}),$(".developers-header").each(function(){$(this).click(function(){var e=$(".developers");$(this).hasClass("developers-header-collapsed")?($(this).removeClass("developers-header-collapsed"),$(this).addClass("developers-header-expanded"),e.removeClass("developers-collapsed"),e.addClass("developers-expanded")):($(this).removeClass("developers-header-expanded"),$(this).addClass("developers-header-collapsed"),e.removeClass("developers-expanded"),e.addClass("developers-collapsed"))})}),dbf_reg_handlers(),$(".content .app").length&&(setTimeout(reload_app,3e4),history.replaceState({page:"station"},document.title,"")),window.onpopstate=function(a){var t;null!=a.state?"station"==a.state.page?($(".moreinfo").each(function(){$(this).removeClass("expanded-moreinfo"),$(this).addClass("collapsed-moreinfo")}),$("div.app > ul").length||($("div.app").append("<ul></ul>"),reload_app())):"traindetail"==a.state.page&&(t=!1,$("div.app > ul > li").each(function(){var e=$(this);e.data("no")==a.state.train&&(dbf_show_moreinfo(e,!0),t=!0)}),t||($(".moreinfo").each(function(){$(this).removeClass("collapsed-moreinfo"),$(this).addClass("expanded-moreinfo")}),$(".moreinfo .mfooter").append("Der Zug ist abgefahren (Zug nicht gefunden)"))):console.log("unhandled popstate! "+document.location)}});
diff --git a/public/static/js/geostop.js b/public/static/js/geostop.js
new file mode 100644
index 0000000..69bb607
--- /dev/null
+++ b/public/static/js/geostop.js
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 Birte Kristina Friesel
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+$(function() {
+ const removeStatus = function() {
+ $('div.candidatestatus').remove();
+ };
+ const showError = function(header, message, code) {
+ const errnode = $(document.createElement('div'));
+ errnode.attr('class', 'error');
+ errnode.text(message);
+
+ const headnode = $(document.createElement('strong'));
+ headnode.text(header);
+ errnode.prepend(headnode);
+
+ if (code) {
+ const shortnode = $(document.createElement('div'));
+ shortnode.attr('class', 'errcode');
+ shortnode.text(code);
+ errnode.append(shortnode);
+ }
+
+ $('div.candidatelist').append(errnode);
+ };
+
+ const processResult = function(data) {
+ removeStatus();
+ if (data.error) {
+ showError('Backend-Fehler:', data.error, null);
+ } else if (data.candidates.length == 0) {
+ showError('Keine Stationen in 70km Umkreis gefunden', '', null);
+ } else {
+ $.each(data.candidates, function(i, candidate) {
+
+ const eva = candidate.eva,
+ name = candidate.name,
+ distance = candidate.distance.toFixed(1),
+ efa = candidate.efa,
+ hafas = candidate.hafas;
+
+ const stationlink = $(document.createElement('a'));
+ if (efa) {
+ stationlink.attr('href', eva + '?efa=' + efa);
+ } else if (hafas) {
+ stationlink.attr('href', eva + '?hafas=' + hafas);
+ } else {
+ stationlink.attr('href', eva);
+ }
+ stationlink.text(name + ' ');
+
+ const distancenode = $(document.createElement('div'));
+ distancenode.attr('class', 'distance');
+ distancenode.text(distance);
+
+ const icon = $(document.createElement('i'));
+ icon.attr('class', 'material-icons');
+ icon.text((hafas || efa) ? 'directions' : 'train');
+
+ stationlink.append(icon);
+ stationlink.append(distancenode);
+ $('div.candidatelist').append(stationlink);
+ });
+ }
+ };
+
+ const processLocation = function(loc) {
+ const param = new URLSearchParams(window.location.search);
+ $.post('/_geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude, efa: param.get('efa'), hafas: param.get('hafas')}, processResult).fail(function(jqXHR, textStatus, errorThrown) {
+ removeStatus();
+ showError("Netzwerkfehler: ", textStatus, errorThrown);
+ });
+ $('div.candidatestatus').text('Suche Stationen…');
+ };
+
+ const processError = function(error) {
+ removeStatus();
+ if (error.code == error.PERMISSION_DENIED) {
+ showError('Standortanfrage nicht möglich.', 'Vermutlich fehlen die Rechte im Browser oder der Android Location Service ist deaktiviert.', 'geolocation.error.PERMISSION_DENIED');
+ } else if (error.code == error.POSITION_UNAVAILABLE) {
+ showError('Standort konnte nicht ermittelt werden', '(Service nicht verfügbar)', 'geolocation.error.POSITION_UNAVAILABLE');
+ } else if (error.code == error.TIMEOUT) {
+ showError('Standort konnte nicht ermittelt werden', '(Timeout)', 'geolocation.error.TIMEOUT');
+ } else {
+ showError('Standort konnte nicht ermittelt werden', '(unbekannter Fehler)', 'unknown geolocation.error code');
+ }
+ };
+
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(processLocation, processError);
+ $('div.candidatestatus').text('Position wird bestimmt…');
+ } else {
+ removeStatus();
+ showError('Standortanfragen werden von diesem Browser nicht unterstützt', '', null);
+ }
+});
diff --git a/public/static/js/geostop.min.js b/public/static/js/geostop.min.js
new file mode 100644
index 0000000..5998966
--- /dev/null
+++ b/public/static/js/geostop.min.js
@@ -0,0 +1 @@
+$(function(){function n(e){a(),e.error?r("Backend-Fehler:",e.error,null):0==e.candidates.length?r("Keine Stationen in 70km Umkreis gefunden","",null):$.each(e.candidates,function(e,t){var n=t.eva,a=t.name,r=t.distance.toFixed(1),o=t.efa,t=t.hafas,i=$(document.createElement("a")),n=(o?i.attr("href",n+"?efa="+o):t?i.attr("href",n+"?hafas="+t):i.attr("href",n),i.text(a+" "),$(document.createElement("div"))),a=(n.attr("class","distance"),n.text(r),$(document.createElement("i")));a.attr("class","material-icons"),a.text(t||o?"directions":"train"),i.append(a),i.append(n),$("div.candidatelist").append(i)})}const a=function(){$("div.candidatestatus").remove()},r=function(e,t,n){var a=$(document.createElement("div")),t=(a.attr("class","error"),a.text(t),$(document.createElement("strong")));t.text(e),a.prepend(t),n&&((e=$(document.createElement("div"))).attr("class","errcode"),e.text(n),a.append(e)),$("div.candidatelist").append(a)};navigator.geolocation?(navigator.geolocation.getCurrentPosition(function(e){var t=new URLSearchParams(window.location.search);$.post("/_geolocation",{lon:e.coords.longitude,lat:e.coords.latitude,efa:t.get("efa"),hafas:t.get("hafas")},n).fail(function(e,t,n){a(),r("Netzwerkfehler: ",t,n)}),$("div.candidatestatus").text("Suche Stationen…")},function(e){a(),e.code==e.PERMISSION_DENIED?r("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?r("Standort konnte nicht ermittelt werden","(Service nicht verfügbar)","geolocation.error.POSITION_UNAVAILABLE"):e.code==e.TIMEOUT?r("Standort konnte nicht ermittelt werden","(Timeout)","geolocation.error.TIMEOUT"):r("Standort konnte nicht ermittelt werden","(unbekannter Fehler)","unknown geolocation.error code")}),$("div.candidatestatus").text("Position wird bestimmt…")):(a(),r("Standortanfragen werden von diesem Browser nicht unterstützt","",null))});
diff --git a/public/static/js/jquery-3.4.1.min.js b/public/static/js/jquery-3.4.1.min.js
new file mode 100644
index 0000000..a1c07fd
--- /dev/null
+++ b/public/static/js/jquery-3.4.1.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}k.fn=k.prototype={jquery:f,constructor:k,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=k.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return k.each(this,e)},map:function(n){return this.pushStack(k.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},k.extend=k.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(k.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||k.isPlainObject(n)?n:{},i=!1,a[t]=k.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},k.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t){b(e,{nonce:t&&t.nonce})},each:function(e,t){var n,r=0;if(d(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?"":(e+"").replace(p,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(d(Object(e))?k.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(d(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g.apply([],a)},guid:1,support:y}),"function"==typeof Symbol&&(k.fn[Symbol.iterator]=t[Symbol.iterator]),k.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var h=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,k="sizzle"+1*new Date,m=n.document,S=0,r=0,p=ue(),x=ue(),N=ue(),A=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",$=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",F=new RegExp(M+"+","g"),B=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="<a id='"+k+"'></a><select id='"+k+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!==C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!==C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&j.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(D),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(F," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),x="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[k]||(a[k]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===S&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[S,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[k]||(a[k]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===S&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[k]||(a[k]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[S,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[k]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace(B,"$1"));return s[k]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[S,p];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[k]||(e[k]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===S&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[k]&&(v=Ce(v)),y&&!y[k]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[k]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(B,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace(B," ")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=N[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[k]?i.push(a):o.push(a);(a=N(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=S+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t===C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument===C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(S=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(S=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=k.split("").sort(D).join("")===k,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);k.find=h,k.expr=h.selectors,k.expr[":"]=k.expr.pseudos,k.uniqueSort=k.unique=h.uniqueSort,k.text=h.getText,k.isXMLDoc=h.isXML,k.contains=h.contains,k.escapeSelector=h.escape;var T=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&k(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},N=k.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var D=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1<i.call(n,e)!==r}):k.filter(n,e,r)}k.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?k.find.matchesSelector(r,e)?[r]:[]:k.find.matches(e,k.grep(t,function(e){return 1===e.nodeType}))},k.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(k(e).filter(function(){for(t=0;t<r;t++)if(k.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)k.find(e,i[t],n);return 1<r?k.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&N.test(e)?k(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(k.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&k(e);if(!N.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&k.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?k.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(k(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(k.uniqueSort(k.merge(this.get(),k(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),k.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return T(e,"parentNode")},parentsUntil:function(e,t,n){return T(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return T(e,"nextSibling")},prevAll:function(e){return T(e,"previousSibling")},nextUntil:function(e,t,n){return T(e,"nextSibling",n)},prevUntil:function(e,t,n){return T(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return"undefined"!=typeof e.contentDocument?e.contentDocument:(A(e,"template")&&(e=e.content||e),k.merge([],e.childNodes))}},function(r,i){k.fn[r]=function(e,t){var n=k.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=k.filter(t,n)),1<this.length&&(O[r]||k.uniqueSort(n),H.test(r)&&n.reverse()),this.pushStack(n)}});var R=/[^\x20\t\r\n\f]+/g;function M(e){return e}function I(e){throw e}function W(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}k.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},k.each(e.match(R)||[],function(e,t){n[t]=!0}),n):k.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){k.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return k.each(arguments,function(e,t){var n;while(-1<(n=k.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<k.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},k.extend({Deferred:function(e){var o=[["notify","progress",k.Callbacks("memory"),k.Callbacks("memory"),2],["resolve","done",k.Callbacks("once memory"),k.Callbacks("once memory"),0,"resolved"],["reject","fail",k.Callbacks("once memory"),k.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return k.Deferred(function(r){k.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,M,s),l(u,o,I,s)):(u++,t.call(e,l(u,o,M,s),l(u,o,I,s),l(u,o,M,o.notifyWith))):(a!==M&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){k.Deferred.exceptionHook&&k.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==I&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(k.Deferred.getStackHook&&(t.stackTrace=k.Deferred.getStackHook()),C.setTimeout(t))}}return k.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:M,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:M)),o[2][3].add(l(0,e,m(n)?n:I))}).promise()},promise:function(e){return null!=e?k.extend(e,a):a}},s={};return k.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=k.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(W(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)W(i[t],a(t),o.reject);return o.promise()}});var $=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;k.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&$.test(e.name)&&C.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},k.readyException=function(e){C.setTimeout(function(){throw e})};var F=k.Deferred();function B(){E.removeEventListener("DOMContentLoaded",B),C.removeEventListener("load",B),k.ready()}k.fn.ready=function(e){return F.then(e)["catch"](function(e){k.readyException(e)}),this},k.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--k.readyWait:k.isReady)||(k.isReady=!0)!==e&&0<--k.readyWait||F.resolveWith(E,[k])}}),k.ready.then=F.then,"complete"===E.readyState||"loading"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(k.ready):(E.addEventListener("DOMContentLoaded",B),C.addEventListener("load",B));var _=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===w(n))for(s in i=!0,n)_(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(k(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},z=/^-ms-/,U=/-([a-z])/g;function X(e,t){return t.toUpperCase()}function V(e){return e.replace(z,"ms-").replace(U,X)}var G=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function Y(){this.expando=k.expando+Y.uid++}Y.uid=1,Y.prototype={cache:function(e){var t=e[this.expando];return t||(t={},G(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[V(t)]=n;else for(r in t)i[V(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][V(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(V):(t=V(t))in r?[t]:t.match(R)||[]).length;while(n--)delete r[t[n]]}(void 0===t||k.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!k.isEmptyObject(t)}};var Q=new Y,J=new Y,K=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function ee(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(Z,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:K.test(i)?JSON.parse(i):i)}catch(e){}J.set(e,t,n)}else n=void 0;return n}k.extend({hasData:function(e){return J.hasData(e)||Q.hasData(e)},data:function(e,t,n){return J.access(e,t,n)},removeData:function(e,t){J.remove(e,t)},_data:function(e,t,n){return Q.access(e,t,n)},_removeData:function(e,t){Q.remove(e,t)}}),k.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=J.get(o),1===o.nodeType&&!Q.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=V(r.slice(5)),ee(o,r,i[r]));Q.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){J.set(this,n)}):_(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=J.get(o,n))?t:void 0!==(t=ee(o,n))?t:void 0;this.each(function(){J.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){J.remove(this,e)})}}),k.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Q.get(e,t),n&&(!r||Array.isArray(n)?r=Q.access(e,t,k.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=k.queue(e,t),r=n.length,i=n.shift(),o=k._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){k.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Q.get(e,n)||Q.access(e,n,{empty:k.Callbacks("once memory").add(function(){Q.remove(e,[t+"queue",n])})})}}),k.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?k.queue(this[0],t):void 0===n?this:this.each(function(){var e=k.queue(this,t,n);k._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&k.dequeue(this,t)})},dequeue:function(e){return this.each(function(){k.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=k.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Q.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var te=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ne=new RegExp("^(?:([+-])=|)("+te+")([a-z%]*)$","i"),re=["Top","Right","Bottom","Left"],ie=E.documentElement,oe=function(e){return k.contains(e.ownerDocument,e)},ae={composed:!0};ie.getRootNode&&(oe=function(e){return k.contains(e.ownerDocument,e)||e.getRootNode(ae)===e.ownerDocument});var se=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&oe(e)&&"none"===k.css(e,"display")},ue=function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];for(o in i=n.apply(e,r||[]),t)e.style[o]=a[o];return i};function le(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return k.css(e,t,"")},u=s(),l=n&&n[3]||(k.cssNumber[t]?"":"px"),c=e.nodeType&&(k.cssNumber[t]||"px"!==l&&+u)&&ne.exec(k.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)k.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,k.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ce={};function fe(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Q.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&se(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ce[s])||(o=a.body.appendChild(a.createElement(s)),u=k.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ce[s]=u)))):"none"!==n&&(l[c]="none",Q.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}k.fn.extend({show:function(){return fe(this,!0)},hide:function(){return fe(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){se(this)?k(this).show():k(this).hide()})}});var pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Q.set(e[n],"globalEval",!t||Q.get(t[n],"globalEval"))}ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;var me,xe,be=/<|&#?\w+;/;function we(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===w(o))k.merge(p,o.nodeType?[o]:o);else if(be.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+k.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;k.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<k.inArray(o,r))i&&i.push(o);else if(l=oe(o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}me=E.createDocumentFragment().appendChild(E.createElement("div")),(xe=E.createElement("input")).setAttribute("type","radio"),xe.setAttribute("checked","checked"),xe.setAttribute("name","t"),me.appendChild(xe),y.checkClone=me.cloneNode(!0).cloneNode(!0).lastChild.checked,me.innerHTML="<textarea>x</textarea>",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t<arguments.length;t++)u[t]=arguments[t];if(s.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,s)){a=k.event.handlers.call(this,s,l),t=0;while((i=a[t++])&&!s.isPropagationStopped()){s.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!s.isImmediatePropagationStopped())s.rnamespace&&!1!==o.namespace&&!s.rnamespace.test(o.namespace)||(s.handleObj=o,s.data=o.data,void 0!==(r=((k.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,u))&&!1===(s.result=r)&&(s.preventDefault(),s.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,s),s.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<k(i,this).index(l):k.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(k.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[k.expando]?e:new k.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&De(t,"click",ke),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&De(t,"click"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,"input")&&Q.get(t,"click")||A(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},k.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},k.Event=function(e,t){if(!(this instanceof k.Event))return new k.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?ke:Se,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&k.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[k.expando]=!0},k.Event.prototype={constructor:k.Event,isDefaultPrevented:Se,isPropagationStopped:Se,isImmediatePropagationStopped:Se,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=ke,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=ke,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=ke,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},k.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&Te.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&Ce.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},k.event.addProp),k.each({focus:"focusin",blur:"focusout"},function(e,t){k.event.special[e]={setup:function(){return De(this,e,Ne),!1},trigger:function(){return De(this,e),!0},delegateType:t}}),k.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){k.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||k.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),k.fn.extend({on:function(e,t,n,r){return Ae(this,e,t,n,r)},one:function(e,t,n,r){return Ae(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,k(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Se),this.each(function(){k.event.remove(this,e,n,t)})}});var je=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/<script|<style|<link/i,Le=/checked\s*(?:[^=]|=\s*.checked.)/i,He=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n<r;n++)k.event.add(t,i,l[i][n]);J.hasData(e)&&(s=J.access(e),u=k.extend({},s),J.set(t,u))}}function Ie(n,r,i,o){r=g.apply([],r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&"string"==typeof d&&!y.checkClone&&Le.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),Ie(t,r,i,o)});if(f&&(t=(e=we(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=k.map(ve(e,"script"),Pe)).length;c<f;c++)u=e,c!==p&&(u=k.clone(u,!0,!0),s&&k.merge(a,ve(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,k.map(a,Re),c=0;c<s;c++)u=a[c],he.test(u.type||"")&&!Q.access(u,"globalEval")&&k.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?k._evalUrl&&!u.noModule&&k._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")}):b(u.textContent.replace(He,""),u,l))}return n}function We(e,t,n){for(var r,i=t?k.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||k.cleanData(ve(r)),r.parentNode&&(n&&oe(r)&&ye(ve(r,"script")),r.parentNode.removeChild(r));return e}k.extend({htmlPrefilter:function(e){return e.replace(je,"<$1></$2>")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Me(o[r],a[r]);else Me(e,c);return 0<(a=ve(c,"script")).length&&ye(a,!f&&ve(e,"script")),c},cleanData:function(e){for(var t,n,r,i=k.event.special,o=0;void 0!==(n=e[o]);o++)if(G(n)){if(t=n[Q.expando]){if(t.events)for(r in t.events)i[r]?k.event.remove(n,r):k.removeEvent(n,r,t.handle);n[Q.expando]=void 0}n[J.expando]&&(n[J.expando]=void 0)}}}),k.fn.extend({detach:function(e){return We(this,e,!0)},remove:function(e){return We(this,e)},text:function(e){return _(this,function(e){return void 0===e?k.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Ie(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Oe(this,e).appendChild(e)})},prepend:function(){return Ie(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Oe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Ie(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Ie(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(k.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return k.clone(this,e,t)})},html:function(e){return _(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!qe.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=k.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(k.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Ie(this,arguments,function(e){var t=this.parentNode;k.inArray(this,n)<0&&(k.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),k.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){k.fn[e]=function(e){for(var t,n=[],r=k(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),k(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var $e=new RegExp("^("+te+")(?!px)[a-z%]+$","i"),Fe=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},Be=new RegExp(re.join("|"),"i");function _e(e,t,n){var r,i,o,a,s=e.style;return(n=n||Fe(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||oe(e)||(a=k.style(e,t)),!y.pixelBoxStyles()&&$e.test(a)&&Be.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function ze(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(u){s.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",u.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",ie.appendChild(s).appendChild(u);var e=C.getComputedStyle(u);n="1%"!==e.top,a=12===t(e.marginLeft),u.style.right="60%",o=36===t(e.right),r=36===t(e.width),u.style.position="absolute",i=12===t(u.offsetWidth/3),ie.removeChild(s),u=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s=E.createElement("div"),u=E.createElement("div");u.style&&(u.style.backgroundClip="content-box",u.cloneNode(!0).style.backgroundClip="",y.clearCloneStyle="content-box"===u.style.backgroundClip,k.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),a},scrollboxSize:function(){return e(),i}}))}();var Ue=["Webkit","Moz","ms"],Xe=E.createElement("div").style,Ve={};function Ge(e){var t=k.cssProps[e]||Ve[e];return t||(e in Xe?e:Ve[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=Ue.length;while(n--)if((e=Ue[n]+t)in Xe)return e}(e)||e)}var Ye=/^(none|table(?!-c[ea]).+)/,Qe=/^--/,Je={position:"absolute",visibility:"hidden",display:"block"},Ke={letterSpacing:"0",fontWeight:"400"};function Ze(e,t,n){var r=ne.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function et(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=k.css(e,n+re[a],!0,i)),r?("content"===n&&(u-=k.css(e,"padding"+re[a],!0,i)),"margin"!==n&&(u-=k.css(e,"border"+re[a]+"Width",!0,i))):(u+=k.css(e,"padding"+re[a],!0,i),"padding"!==n?u+=k.css(e,"border"+re[a]+"Width",!0,i):s+=k.css(e,"border"+re[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function tt(e,t,n){var r=Fe(e),i=(!y.boxSizingReliable()||n)&&"border-box"===k.css(e,"boxSizing",!1,r),o=i,a=_e(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if($e.test(a)){if(!n)return a;a="auto"}return(!y.boxSizingReliable()&&i||"auto"===a||!parseFloat(a)&&"inline"===k.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===k.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+et(e,t,n||(i?"border":"content"),o,r,a)+"px"}function nt(e,t,n,r,i){return new nt.prototype.init(e,t,n,r,i)}k.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=_e(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=V(t),u=Qe.test(t),l=e.style;if(u||(t=Ge(s)),a=k.cssHooks[t]||k.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=ne.exec(n))&&i[1]&&(n=le(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(k.cssNumber[s]?"":"px")),y.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=V(t);return Qe.test(t)||(t=Ge(s)),(a=k.cssHooks[t]||k.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=_e(e,t,r)),"normal"===i&&t in Ke&&(i=Ke[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),k.each(["height","width"],function(e,u){k.cssHooks[u]={get:function(e,t,n){if(t)return!Ye.test(k.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?tt(e,u,n):ue(e,Je,function(){return tt(e,u,n)})},set:function(e,t,n){var r,i=Fe(e),o=!y.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===k.css(e,"boxSizing",!1,i),s=n?et(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-et(e,u,"border",!1,i)-.5)),s&&(r=ne.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=k.css(e,u)),Ze(0,t,s)}}}),k.cssHooks.marginLeft=ze(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(_e(e,"marginLeft"))||e.getBoundingClientRect().left-ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),k.each({margin:"",padding:"",border:"Width"},function(i,o){k.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+re[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(k.cssHooks[i+o].set=Ze)}),k.fn.extend({css:function(e,t){return _(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Fe(e),i=t.length;a<i;a++)o[t[a]]=k.css(e,t[a],!1,r);return o}return void 0!==n?k.style(e,t,n):k.css(e,t)},e,t,1<arguments.length)}}),((k.Tween=nt).prototype={constructor:nt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||k.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(k.cssNumber[n]?"":"px")},cur:function(){var e=nt.propHooks[this.prop];return e&&e.get?e.get(this):nt.propHooks._default.get(this)},run:function(e){var t,n=nt.propHooks[this.prop];return this.options.duration?this.pos=t=k.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):nt.propHooks._default.set(this),this}}).init.prototype=nt.prototype,(nt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=k.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){k.fx.step[e.prop]?k.fx.step[e.prop](e):1!==e.elem.nodeType||!k.cssHooks[e.prop]&&null==e.elem.style[Ge(e.prop)]?e.elem[e.prop]=e.now:k.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=nt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},k.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},k.fx=nt.prototype.init,k.fx.step={};var rt,it,ot,at,st=/^(?:toggle|show|hide)$/,ut=/queueHooks$/;function lt(){it&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(lt):C.setTimeout(lt,k.fx.interval),k.fx.tick())}function ct(){return C.setTimeout(function(){rt=void 0}),rt=Date.now()}function ft(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=re[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function pt(e,t,n){for(var r,i=(dt.tweeners[t]||[]).concat(dt.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function dt(o,e,t){var n,a,r=0,i=dt.prefilters.length,s=k.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=rt||ct(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:k.extend({},e),opts:k.extend(!0,{specialEasing:{},easing:k.easing._default},t),originalProperties:e,originalOptions:t,startTime:rt||ct(),duration:t.duration,tweens:[],createTween:function(e,t){var n=k.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=V(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=k.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=dt.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(k._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return k.map(c,pt,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),k.fx.timer(k.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}k.Animation=k.extend(dt,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return le(n.elem,e,ne.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=["*"]):e=e.match(R);for(var n,r=0,i=e.length;r<i;r++)n=e[r],dt.tweeners[n]=dt.tweeners[n]||[],dt.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&se(e),v=Q.get(e,"fxshow");for(r in n.queue||(null==(a=k._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,k.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],st.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||k.style(e,r)}if((u=!k.isEmptyObject(t))||!k.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Q.get(e,"display")),"none"===(c=k.css(e,"display"))&&(l?c=l:(fe([e],!0),l=e.style.display||l,c=k.css(e,"display"),fe([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===k.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=Q.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&fe([e],!0),p.done(function(){for(r in g||fe([e]),Q.remove(e,"fxshow"),d)k.style(e,r,d[r])})),u=pt(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?dt.prefilters.unshift(e):dt.prefilters.push(e)}}),k.speed=function(e,t,n){var r=e&&"object"==typeof e?k.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return k.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in k.fx.speeds?r.duration=k.fx.speeds[r.duration]:r.duration=k.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&k.dequeue(this,r.queue)},r},k.fn.extend({fadeTo:function(e,t,n,r){return this.filter(se).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=k.isEmptyObject(t),o=k.speed(e,n,r),a=function(){var e=dt(this,k.extend({},t),o);(i||Q.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&!1!==i&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=k.timers,r=Q.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&ut.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||k.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=Q.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=k.timers,o=n?n.length:0;for(t.finish=!0,k.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),k.each(["toggle","show","hide"],function(e,r){var i=k.fn[r];k.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(ft(r,!0),e,t,n)}}),k.each({slideDown:ft("show"),slideUp:ft("hide"),slideToggle:ft("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){k.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),k.timers=[],k.fx.tick=function(){var e,t=0,n=k.timers;for(rt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||k.fx.stop(),rt=void 0},k.fx.timer=function(e){k.timers.push(e),k.fx.start()},k.fx.interval=13,k.fx.start=function(){it||(it=!0,lt())},k.fx.stop=function(){it=null},k.fx.speeds={slow:600,fast:200,_default:400},k.fn.delay=function(r,e){return r=k.fx&&k.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},ot=E.createElement("input"),at=E.createElement("select").appendChild(E.createElement("option")),ot.type="checkbox",y.checkOn=""!==ot.value,y.optSelected=at.selected,(ot=E.createElement("input")).value="t",ot.type="radio",y.radioValue="t"===ot.value;var ht,gt=k.expr.attrHandle;k.fn.extend({attr:function(e,t){return _(this,k.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){k.removeAttr(this,e)})}}),k.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?k.prop(e,t,n):(1===o&&k.isXMLDoc(e)||(i=k.attrHooks[t.toLowerCase()]||(k.expr.match.bool.test(t)?ht:void 0)),void 0!==n?null===n?void k.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=k.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&"radio"===t&&A(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(R);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),ht={set:function(e,t,n){return!1===t?k.removeAttr(e,n):e.setAttribute(n,n),n}},k.each(k.expr.match.bool.source.match(/\w+/g),function(e,t){var a=gt[t]||k.find.attr;gt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=gt[o],gt[o]=r,r=null!=a(e,t,n)?o:null,gt[o]=i),r}});var vt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;function mt(e){return(e.match(R)||[]).join(" ")}function xt(e){return e.getAttribute&&e.getAttribute("class")||""}function bt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(R)||[]}k.fn.extend({prop:function(e,t){return _(this,k.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[k.propFix[e]||e]})}}),k.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&k.isXMLDoc(e)||(t=k.propFix[t]||t,i=k.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=k.find.attr(e,"tabindex");return t?parseInt(t,10):vt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),y.optSelected||(k.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),k.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){k.propFix[this.toLowerCase()]=this}),k.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){k(this).addClass(t.call(this,e,xt(this)))});if((e=bt(t)).length)while(n=this[u++])if(i=xt(n),r=1===n.nodeType&&" "+mt(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=mt(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){k(this).removeClass(t.call(this,e,xt(this)))});if(!arguments.length)return this.attr("class","");if((e=bt(t)).length)while(n=this[u++])if(i=xt(n),r=1===n.nodeType&&" "+mt(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=mt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){k(this).toggleClass(i.call(this,e,xt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=k(this),r=bt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=xt(this))&&Q.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Q.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+mt(xt(n))+" ").indexOf(t))return!0;return!1}});var wt=/\r/g;k.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,k(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=k.map(t,function(e){return null==e?"":e+""})),(r=k.valHooks[this.type]||k.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=k.valHooks[t.type]||k.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(wt,""):null==e?"":e:void 0}}),k.extend({valHooks:{option:{get:function(e){var t=k.find.attr(e,"value");return null!=t?t:mt(k.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,"optgroup"))){if(t=k(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=k.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<k.inArray(k.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),k.each(["radio","checkbox"],function(){k.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<k.inArray(k(e).val(),t)}},y.checkOn||(k.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),y.focusin="onfocusin"in C;var Tt=/^(?:focusinfocus|focusoutblur)$/,Ct=function(e){e.stopPropagation()};k.extend(k.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,"type")?e.type:e,h=v.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!Tt.test(d+k.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[k.expando]?e:new k.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:k.makeArray(t,[e]),c=k.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,Tt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Q.get(o,"events")||{})[e.type]&&Q.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&G(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!G(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),k.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,Ct),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,Ct),k.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=k.extend(new k.Event,n,{type:e,isSimulated:!0});k.event.trigger(r,null,t)}}),k.fn.extend({trigger:function(e,t){return this.each(function(){k.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return k.event.trigger(e,t,n,!0)}}),y.focusin||k.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){k.event.simulate(r,e.target,k.event.fix(e))};k.event.special[r]={setup:function(){var e=this.ownerDocument||this,t=Q.access(e,r);t||e.addEventListener(n,i,!0),Q.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this,t=Q.access(e,r)-1;t?Q.access(e,r,t):(e.removeEventListener(n,i,!0),Q.remove(e,r))}}});var Et=C.location,kt=Date.now(),St=/\?/;k.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||k.error("Invalid XML: "+e),t};var Nt=/\[\]$/,At=/\r?\n/g,Dt=/^(?:submit|button|image|reset|file)$/i,jt=/^(?:input|select|textarea|keygen)/i;function qt(n,e,r,i){var t;if(Array.isArray(e))k.each(e,function(e,t){r||Nt.test(n)?i(n,t):qt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==w(e))i(n,e);else for(t in e)qt(n+"["+t+"]",e[t],r,i)}k.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!k.isPlainObject(e))k.each(e,function(){i(this.name,this.value)});else for(n in e)qt(n,e[n],t,i);return r.join("&")},k.fn.extend({serialize:function(){return k.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=k.prop(this,"elements");return e?k.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!k(this).is(":disabled")&&jt.test(this.nodeName)&&!Dt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=k(this).val();return null==n?null:Array.isArray(n)?k.map(n,function(e){return{name:t.name,value:e.replace(At,"\r\n")}}):{name:t.name,value:n.replace(At,"\r\n")}}).get()}});var Lt=/%20/g,Ht=/#.*$/,Ot=/([?&])_=[^&]*/,Pt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Rt=/^(?:GET|HEAD)$/,Mt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Ft=E.createElement("a");function Bt(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(R)||[];if(m(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function _t(t,i,o,a){var s={},u=t===Wt;function l(e){var r;return s[e]=!0,k.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function zt(e,t){var n,r,i=k.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&k.extend(!0,e,r),e}Ft.href=Et.href,k.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Et.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Et.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":k.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,k.ajaxSettings),t):zt(k.ajaxSettings,e)},ajaxPrefilter:Bt(It),ajaxTransport:Bt(Wt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=k.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?k(y):k.event,x=k.Deferred(),b=k.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Pt.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Et.href)+"").replace(Mt,Et.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(R)||[""],null==v.crossDomain){r=E.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Ft.protocol+"//"+Ft.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=k.param(v.data,v.traditional)),_t(It,v,t,T),h)return T;for(i in(g=k.event&&v.global)&&0==k.active++&&k.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Rt.test(v.type),f=v.url.replace(Ht,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(Lt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(St.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Ot,"$1"),o=(St.test(f)?"&":"?")+"_="+kt+++o),v.url=f+o),v.ifModified&&(k.lastModified[f]&&T.setRequestHeader("If-Modified-Since",k.lastModified[f]),k.etag[f]&&T.setRequestHeader("If-None-Match",k.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+$t+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=_t(Wt,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(k.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(k.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--k.active||k.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return k.get(e,t,n,"json")},getScript:function(e,t){return k.get(e,void 0,t,"script")}}),k.each(["get","post"],function(e,i){k[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),k.ajax(k.extend({url:e,type:i,dataType:r,data:t,success:n},k.isPlainObject(e)&&e))}}),k._evalUrl=function(e,t){return k.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){k.globalEval(e,t)}})},k.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=k(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){k(this).wrapInner(n.call(this,e))}):this.each(function(){var e=k(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){k(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){k(this).replaceWith(this.childNodes)}),this}}),k.expr.pseudos.hidden=function(e){return!k.expr.pseudos.visible(e)},k.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},k.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var Ut={0:200,1223:204},Xt=k.ajaxSettings.xhr();y.cors=!!Xt&&"withCredentials"in Xt,y.ajax=Xt=!!Xt,k.ajaxTransport(function(i){var o,a;if(y.cors||Xt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(Ut[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),k.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),k.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return k.globalEval(e),e}}}),k.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),k.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=k("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=mt(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&k.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?k("<div>").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}}),k.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),k.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),k.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||k.guid++,i},k.holdReady=function(e){e?k.readyWait++:k.ready(!0)},k.isArray=Array.isArray,k.parseJSON=JSON.parse,k.nodeName=A,k.isFunction=m,k.isWindow=x,k.camelCase=V,k.type=w,k.now=Date.now,k.isNumeric=function(e){var t=k.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},"function"==typeof define&&define.amd&&define("jquery",[],function(){return k});var Qt=C.jQuery,Jt=C.$;return k.noConflict=function(e){return C.$===k&&(C.$=Jt),e&&C.jQuery===k&&(C.jQuery=Qt),k},e||(C.jQuery=C.$=k),k});
diff --git a/public/static/js/jquery-ui.min.js b/public/static/js/jquery-ui.min.js
new file mode 100644
index 0000000..e7082c3
--- /dev/null
+++ b/public/static/js/jquery-ui.min.js
@@ -0,0 +1,6 @@
+/*! jQuery UI - v1.12.1 - 2019-05-04
+* http://jqueryui.com
+* Includes: widget.js, position.js, keycode.js, unique-id.js, widgets/autocomplete.js, widgets/menu.js
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){t.ui=t.ui||{},t.ui.version="1.12.1";var e=0,i=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},l=e.split(".")[0];e=e.split(".")[1];var h=l+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][h.toLowerCase()]=function(e){return!!t.data(e,h)},t[l]=t[l]||{},n=t[l][e],o=t[l][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:l,widgetName:e,widgetFullName:h}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var s,n,o=i.call(arguments,1),a=0,r=o.length;r>a;a++)for(s in o[a])n=o[a][s],o[a].hasOwnProperty(s)&&void 0!==n&&(e[s]=t.isPlainObject(n)?t.isPlainObject(e[s])?t.widget.extend({},e[s],n):t.widget.extend({},n):n);return e},t.widget.bridge=function(e,s){var n=s.prototype.widgetFullName||e;t.fn[e]=function(o){var a="string"==typeof o,r=i.call(arguments,1),l=this;return a?this.length||"instance"!==o?this.each(function(){var i,s=t.data(this,n);return"instance"===o?(l=s,!1):s?t.isFunction(s[o])&&"_"!==o.charAt(0)?(i=s[o].apply(s,r),i!==s&&void 0!==i?(l=i&&i.jquery?l.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+o+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+o+"'")}):l=void 0:(r.length&&(o=t.widget.extend.apply(null,[o].concat(r))),this.each(function(){var e=t.data(this,n);e?(e.option(o||{}),e._init&&e._init()):t.data(this,n,new s(o,this))})),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(i,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=e++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),i),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var l=s.match(/^([\w:-]*)\s*(.*)$/),h=l[1]+o.eventNamespace,c=l[2];c?n.on(h,c,r):i.on(h,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,l=/top|center|bottom/,h=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.width<e.element[0].scrollWidth,o="scroll"===s||"auto"===s&&e.height<e.element[0].scrollHeight;return{width:o?t.position.scrollbarWidth():0,height:n?t.position.scrollbarWidth():0}},getWithinInfo:function(e){var i=t(e||window),s=t.isWindow(i[0]),n=!!i[0]&&9===i[0].nodeType,o=!s&&!n;return{element:i,isWindow:s,isDocument:n,offset:o?t(e).offset():{left:0,top:0},scrollLeft:i.scrollLeft(),scrollTop:i.scrollTop(),width:i.outerWidth(),height:i.outerHeight()}}},t.fn.position=function(n){if(!n||!n.of)return d.apply(this,arguments);n=t.extend({},n);var u,p,f,g,m,_,v=t(n.of),b=t.position.getWithinInfo(n.within),y=t.position.getScrollInfo(b),w=(n.collision||"flip").split(" "),k={};return _=s(v),v[0].preventDefault&&(n.at="left top"),p=_.width,f=_.height,g=_.offset,m=t.extend({},g),t.each(["my","at"],function(){var t,e,i=(n[this]||"").split(" ");1===i.length&&(i=r.test(i[0])?i.concat(["center"]):l.test(i[0])?["center"].concat(i):["center","center"]),i[0]=r.test(i[0])?i[0]:"center",i[1]=l.test(i[1])?i[1]:"center",t=h.exec(i[0]),e=h.exec(i[1]),k[this]=[t?t[0]:0,e?e[0]:0],n[this]=[c.exec(i[0])[0],c.exec(i[1])[0]]}),1===w.length&&(w[1]=w[0]),"right"===n.at[0]?m.left+=p:"center"===n.at[0]&&(m.left+=p/2),"bottom"===n.at[1]?m.top+=f:"center"===n.at[1]&&(m.top+=f/2),u=e(k.at,p,f),m.left+=u[0],m.top+=u[1],this.each(function(){var s,r,l=t(this),h=l.outerWidth(),c=l.outerHeight(),d=i(this,"marginLeft"),_=i(this,"marginTop"),x=h+d+i(this,"marginRight")+y.width,C=c+_+i(this,"marginBottom")+y.height,D=t.extend({},m),T=e(k.my,l.outerWidth(),l.outerHeight());"right"===n.my[0]?D.left-=h:"center"===n.my[0]&&(D.left-=h/2),"bottom"===n.my[1]?D.top-=c:"center"===n.my[1]&&(D.top-=c/2),D.left+=T[0],D.top+=T[1],s={marginLeft:d,marginTop:_},t.each(["left","top"],function(e,i){t.ui.position[w[e]]&&t.ui.position[w[e]][i](D,{targetWidth:p,targetHeight:f,elemWidth:h,elemHeight:c,collisionPosition:s,collisionWidth:x,collisionHeight:C,offset:[u[0]+T[0],u[1]+T[1]],my:n.my,at:n.at,within:b,elem:l})}),n.using&&(r=function(t){var e=g.left-D.left,i=e+p-h,s=g.top-D.top,r=s+f-c,u={target:{element:v,left:g.left,top:g.top,width:p,height:f},element:{element:l,left:D.left,top:D.top,width:h,height:c},horizontal:0>i?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};h>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),l.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-r-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-r-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.ui.safeActiveElement=function(t){var e;try{e=t.activeElement}catch(i){e=t.body}return e||(e=t.body),e.nodeName||(e=t.body),e},t.widget("ui.menu",{version:"1.12.1",defaultElement:"<ul>",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,l=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=l.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("<span>").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=l.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()<this.element.prop("scrollHeight")},select:function(e){this.active=this.active||t(e.target).closest(".ui-menu-item");var i={item:this.active};this.active.has(".ui-menu").length||this.collapseAll(e,!0),this._trigger("select",e,i)},_filterMenuItems:function(e){var i=e.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&"),s=RegExp("^"+i,"i");return this.activeMenu.find(this.options.items).filter(".ui-menu-item").filter(function(){return s.test(t.trim(t(this).children(".ui-menu-item-wrapper").text()))})}}),t.widget("ui.autocomplete",{version:"1.12.1",defaultElement:"<input>",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n;this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("<ul>").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(e){e.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,this.element[0]!==t.ui.safeActiveElement(this.document[0])&&this.element.trigger("focus")})},menufocus:function(e,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,e.originalEvent&&/^mouse/.test(e.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){t(e.target).trigger(e.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",e,{item:n})&&e.originalEvent&&/^key/.test(e.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&t.trim(s).length&&(this.liveRegion.children().hide(),t("<div>").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,i){var s=i.item.data("ui-autocomplete-item"),n=this.previous;this.element[0]!==t.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=n,this._delay(function(){this.previous=n,this.selectedItem=s})),!1!==this._trigger("select",e,{item:s})&&this._value(s.value),this.term=this._value(),this.close(e),this.selectedItem=s}}),this.liveRegion=t("<div>",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(e){var i=this.menu.element[0];return e.target===this.element[0]||e.target===i||t.contains(i,e.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_initSource:function(){var e,i,s=this;t.isArray(this.options.source)?(e=this.options.source,this.source=function(i,s){s(t.ui.autocomplete.filter(e,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(e,n){s.xhr&&s.xhr.abort(),s.xhr=t.ajax({url:i,data:e,dataType:"json",success:function(t){n(t)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(t){clearTimeout(this.searching),this.searching=this._delay(function(){var e=this.term===this._value(),i=this.menu.element.is(":visible"),s=t.altKey||t.ctrlKey||t.metaKey||t.shiftKey;(!e||e&&!i&&!s)&&(this.selectedItem=null,this.search(null,t))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length<this.options.minLength?this.close(e):this._trigger("search",e)!==!1?this._search(t):void 0},_search:function(t){this.pending++,this._addClass("ui-autocomplete-loading"),this.cancelSearch=!1,this.source({term:t},this._response())},_response:function(){var e=++this.requestIndex;return t.proxy(function(t){e===this.requestIndex&&this.__response(t),this.pending--,this.pending||this._removeClass("ui-autocomplete-loading")},this)},__response:function(t){t&&(t=this._normalize(t)),this._trigger("response",null,{content:t}),!this.options.disabled&&t&&t.length&&!this.cancelSearch?(this._suggest(t),this._trigger("open")):this._close()},close:function(t){this.cancelSearch=!0,this._close(t)},_close:function(t){this._off(this.document,"mousedown"),this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.blur(),this.isNewMenu=!0,this._trigger("close",t))},_change:function(t){this.previous!==this._value()&&this._trigger("change",t,{item:this.selectedItem})},_normalize:function(e){return e.length&&e[0].label&&e[0].value?e:t.map(e,function(e){return"string"==typeof e?{label:e,value:e}:t.extend({},e,{label:e.label||e.value,value:e.value||e.label})})},_suggest:function(e){var i=this.menu.element.empty();this._renderMenu(i,e),this.isNewMenu=!0,this.menu.refresh(),i.show(),this._resizeMenu(),i.position(t.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next(),this._on(this.document,{mousedown:"_closeOnClickOutside"})},_resizeMenu:function(){var t=this.menu.element;t.outerWidth(Math.max(t.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(e,i){var s=this;t.each(i,function(t,i){s._renderItemData(e,i)})},_renderItemData:function(t,e){return this._renderItem(t,e).data("ui-autocomplete-item",e)},_renderItem:function(e,i){return t("<li>").append(t("<div>").text(i.label)).appendTo(e)},_move:function(t,e){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[t](e),void 0):(this.search(null,e),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(t,e),e.preventDefault())},_isContentEditable:function(t){if(!t.length)return!1;var e=t.prop("contentEditable");return"inherit"===e?this._isContentEditable(t.parent()):"true"===e}}),t.extend(t.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(e,i){var s=RegExp(t.ui.autocomplete.escapeRegex(i),"i");return t.grep(e,function(t){return s.test(t.label||t.value||t)})}}),t.widget("ui.autocomplete",t.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(t>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.children().hide(),t("<div>").text(i).appendTo(this.liveRegion))}}),t.ui.autocomplete}); \ No newline at end of file
diff --git a/public/static/js/map-refresh.js b/public/static/js/map-refresh.js
new file mode 100644
index 0000000..fcaac86
--- /dev/null
+++ b/public/static/js/map-refresh.js
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 Birte Kristina Friesel
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+var j_reqid;
+//var j_stops = [];
+var j_positions = [];
+var j_frame = [];
+var j_frame_i = [];
+
+function dbf_map_parse() {
+ $('#jdata').each(function() {
+ j_reqid = $(this).data('req');
+ /*var route_data = $(this).data('route');
+ if (route_data) {
+ route_data = route_data.split('|');
+ j_stops = [];
+ for (var stop_id in route_data) {
+ var stopdata = route_data[stop_id].split(';');
+ for (var i = 1; i < 5; i++) {
+ stopdata[i] = parseInt(stopdata[i]);
+ }
+ j_stops.push(stopdata);
+ }
+ }*/
+ var positions = $(this).data('poly');
+ if (positions) {
+ positions = positions.split('|');
+ j_positions = [];
+ for (var pos_id in positions) {
+ var posdata = positions[pos_id].split(';');
+ posdata[0] = parseFloat(posdata[0]);
+ posdata[1] = parseFloat(posdata[1]);
+ j_positions.push(posdata);
+ }
+ }
+ });
+}
+
+function dbf_anim_coarse() {
+ if (j_positions.length) {
+ var pos1 = marker.getLatLng();
+ var pos1lat = pos1.lat;
+ var pos1lon = pos1.lng;
+ var pos2 = j_positions.shift();
+ var pos2lat = pos2[0];
+ var pos2lon = pos2[1];
+
+ j_frame_i = 200;
+ j_frame = [];
+
+ // approx 30 Hz -> 60 frames per 2 seconds
+ for (var i = 1; i <= 60; i++) {
+ var ratio = i / 60;
+ j_frame.push([pos1lat + ((pos2lat - pos1lat) * ratio), pos1lon + ((pos2lon - pos1lon) * ratio)]);
+ }
+
+ j_frame_i = 0;
+ }
+}
+
+function dbf_anim_fine() {
+ if (j_frame[j_frame_i]) {
+ marker.setLatLng(j_frame[j_frame_i++]);
+ }
+}
+
+function dbf_map_reload() {
+ const param = new URLSearchParams(window.location.search);
+
+ const new_params = new URLSearchParams();
+ new_params.set('dbris', param.get('dbris') ?? '');
+ new_params.set('motis', param.get('motis') ?? '');
+ new_params.set('efa', param.get('efa') ?? '');
+ new_params.set('hafas', param.get('hafas') ?? '');
+
+ $.get('/_ajax_mapinfo/' + j_reqid + '?' + new_params.toString(), function(data) {
+ $('#infobox').html(data);
+ dbf_map_parse();
+ setTimeout(dbf_map_reload, 61000);
+ }).fail(function() {
+ setTimeout(dbf_map_reload, 5000);
+ });
+}
+
+$(document).ready(function() {
+ if ($('#infobox').length) {
+ dbf_map_parse();
+ setInterval(dbf_anim_coarse, 2000);
+ setInterval(dbf_anim_fine, 33);
+ setTimeout(dbf_map_reload, 61000);
+ }
+});
diff --git a/public/static/js/map-refresh.min.js b/public/static/js/map-refresh.min.js
new file mode 100644
index 0000000..745c922
--- /dev/null
+++ b/public/static/js/map-refresh.min.js
@@ -0,0 +1 @@
+var j_reqid,j_positions=[],j_frame=[],j_frame_i=[];function dbf_map_parse(){$("#jdata").each(function(){j_reqid=$(this).data("req");var a=$(this).data("poly");if(a)for(var e in a=a.split("|"),j_positions=[],a){e=a[e].split(";");e[0]=parseFloat(e[0]),e[1]=parseFloat(e[1]),j_positions.push(e)}})}function dbf_anim_coarse(){if(j_positions.length){var a=marker.getLatLng(),e=a.lat,t=a.lng,a=j_positions.shift(),i=a[0],r=a[1];j_frame_i=200,j_frame=[];for(var _=1;_<=60;_++){var f=_/60;j_frame.push([e+(i-e)*f,t+(r-t)*f])}j_frame_i=0}}function dbf_anim_fine(){j_frame[j_frame_i]&&marker.setLatLng(j_frame[j_frame_i++])}function dbf_map_reload(){var a=new URLSearchParams(window.location.search),e=new URLSearchParams;e.set("dbris",a.get("dbris")??""),e.set("motis",a.get("motis")??""),e.set("efa",a.get("efa")??""),e.set("hafas",a.get("hafas")??""),$.get("/_ajax_mapinfo/"+j_reqid+"?"+e.toString(),function(a){$("#infobox").html(a),dbf_map_parse(),setTimeout(dbf_map_reload,61e3)}).fail(function(){setTimeout(dbf_map_reload,5e3)})}$(document).ready(function(){$("#infobox").length&&(dbf_map_parse(),setInterval(dbf_anim_coarse,2e3),setInterval(dbf_anim_fine,33),setTimeout(dbf_map_reload,61e3))});
diff --git a/public/marquee.js b/public/static/js/marquee.js
index 9e046d5..b64c630 100644
--- a/public/marquee.js
+++ b/public/static/js/marquee.js
@@ -1,6 +1,7 @@
/**
* author Remy Sharp
-* url http://remysharp.com/tag/marquee
+* url https://remysharp.com/2008/09/10/the-silky-smooth-marquee
+* license MIT
*/
(function ($) {
@@ -152,4 +153,4 @@
return $(newMarquee);
};
-}(jQuery)); \ No newline at end of file
+}(jQuery));
diff --git a/public/static/js/marquee.min.js b/public/static/js/marquee.min.js
new file mode 100644
index 0000000..865a9f7
--- /dev/null
+++ b/public/static/js/marquee.min.js
@@ -0,0 +1 @@
+!function(c){c.fn.marquee=function(o){var n=[],h=this.length;function u(t,i,a){var e=a.behavior,r=a.width,s=a.dir;return"alternate"==e?1==t?i[a.widthAxis]-2*r:r:"slide"==e?-1==t?-1==s?i[a.widthAxis]:r:-1==s?i[a.widthAxis]-2*r:0:-1==t?i[a.widthAxis]:0}function p(){for(var t,i=n.length,a=null,e=null,r={},s=[];i--;)a=n[i],r=(e=c(a)).data("marqueeState"),!0!==e.data("paused")?(a[r.axis]+=r.scrollamount*r.dir,t=-1==r.dir?a[r.axis]<=u(-1*r.dir,a,r):a[r.axis]>=u(-1*r.dir,a,r),"scroll"==r.behavior&&r.last==a[r.axis]||"alternate"==r.behavior&&t&&-1!=r.last||"slide"==r.behavior&&t&&-1!=r.last?("alternate"==r.behavior&&(r.dir*=-1),r.last=-1,e.trigger("stop"),r.loops--,0===r.loops?("slide"!=r.behavior?a[r.axis]=u(r.dir,a,r):a[r.axis]=u(-1*r.dir,a,r),e.trigger("end")):(s.push(a),e.trigger("start"),a[r.axis]=u(r.dir,a,r))):s.push(a),r.last=a[r.axis],e.data("marqueeState",r)):s.push(a);(n=s).length&&setTimeout(p,25)}return this.each(function(t){var i=c(this),a=i.attr("width")||i.width(),e=i.attr("height")||i.height(),r=i.after("<div "+(o?'class="'+o+'" ':"")+'style="display: block-inline; width: '+a+"px; height: "+e+'px; overflow: hidden;"><div style="float: left; white-space: nowrap;">'+i.html()+"</div></div>").next(),s=r.get(0),d=(i.attr("direction")||"left").toLowerCase(),l={dir:/down|right/.test(d)?-1:1,axis:/left|right/.test(d)?"scrollLeft":"scrollTop",widthAxis:/left|right/.test(d)?"scrollWidth":"scrollHeight",last:-1,loops:i.attr("loop")||-1,scrollamount:i.attr("scrollamount")||this.scrollAmount||2,behavior:(i.attr("behavior")||"scroll").toLowerCase(),width:/left|right/.test(d)?a:e};-1==i.attr("loop")&&"slide"==l.behavior&&(l.loops=1),i.remove(),/left|right/.test(d)?r.find("> div").css("padding","0 "+a+"px"):r.find("> div").css("padding",e+"px 0"),r.bind("stop",function(){r.data("paused",!0)}).bind("pause",function(){r.data("paused",!0)}).bind("start",function(){r.data("paused",!1)}).bind("unpause",function(){r.data("paused",!1)}).data("marqueeState",l),n.push(s),s[l.axis]=u(l.dir,s,l),r.trigger("start"),t+1==h&&p()}),c(n)}}(jQuery);
diff --git a/public/static/leaflet/images/layers-2x.png b/public/static/leaflet/images/layers-2x.png
new file mode 100644
index 0000000..200c333
--- /dev/null
+++ b/public/static/leaflet/images/layers-2x.png
Binary files differ
diff --git a/public/static/leaflet/images/layers.png b/public/static/leaflet/images/layers.png
new file mode 100644
index 0000000..1a72e57
--- /dev/null
+++ b/public/static/leaflet/images/layers.png
Binary files differ
diff --git a/public/static/leaflet/images/marker-icon-2x-gold.png b/public/static/leaflet/images/marker-icon-2x-gold.png
new file mode 100644
index 0000000..6992d65
--- /dev/null
+++ b/public/static/leaflet/images/marker-icon-2x-gold.png
Binary files differ
diff --git a/public/static/leaflet/images/marker-icon-2x-green.png b/public/static/leaflet/images/marker-icon-2x-green.png
new file mode 100644
index 0000000..c359abb
--- /dev/null
+++ b/public/static/leaflet/images/marker-icon-2x-green.png
Binary files differ
diff --git a/public/static/leaflet/images/marker-icon-2x.png b/public/static/leaflet/images/marker-icon-2x.png
new file mode 100644
index 0000000..88f9e50
--- /dev/null
+++ b/public/static/leaflet/images/marker-icon-2x.png
Binary files differ
diff --git a/public/static/leaflet/images/marker-icon.png b/public/static/leaflet/images/marker-icon.png
new file mode 100644
index 0000000..950edf2
--- /dev/null
+++ b/public/static/leaflet/images/marker-icon.png
Binary files differ
diff --git a/public/static/leaflet/images/marker-shadow.png b/public/static/leaflet/images/marker-shadow.png
new file mode 100644
index 0000000..9fd2979
--- /dev/null
+++ b/public/static/leaflet/images/marker-shadow.png
Binary files differ
diff --git a/public/static/leaflet/leaflet.css b/public/static/leaflet/leaflet.css
new file mode 100644
index 0000000..983d605
--- /dev/null
+++ b/public/static/leaflet/leaflet.css
@@ -0,0 +1,640 @@
+/* required styles */
+
+.leaflet-pane,
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-tile-container,
+.leaflet-pane > svg,
+.leaflet-pane > canvas,
+.leaflet-zoom-box,
+.leaflet-image-layer,
+.leaflet-layer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+.leaflet-container {
+ overflow: hidden;
+ }
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ -webkit-user-drag: none;
+ }
+/* Prevents IE11 from highlighting tiles in blue */
+.leaflet-tile::selection {
+ background: transparent;
+}
+/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
+.leaflet-safari .leaflet-tile {
+ image-rendering: -webkit-optimize-contrast;
+ }
+/* hack that prevents hw layers "stretching" when loading new tiles */
+.leaflet-safari .leaflet-tile-container {
+ width: 1600px;
+ height: 1600px;
+ -webkit-transform-origin: 0 0;
+ }
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+ display: block;
+ }
+/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
+/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
+.leaflet-container .leaflet-overlay-pane svg,
+.leaflet-container .leaflet-marker-pane img,
+.leaflet-container .leaflet-shadow-pane img,
+.leaflet-container .leaflet-tile-pane img,
+.leaflet-container img.leaflet-image-layer,
+.leaflet-container .leaflet-tile {
+ max-width: none !important;
+ max-height: none !important;
+ }
+
+.leaflet-container.leaflet-touch-zoom {
+ -ms-touch-action: pan-x pan-y;
+ touch-action: pan-x pan-y;
+ }
+.leaflet-container.leaflet-touch-drag {
+ -ms-touch-action: pinch-zoom;
+ /* Fallback for FF which doesn't support pinch-zoom */
+ touch-action: none;
+ touch-action: pinch-zoom;
+}
+.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.leaflet-container {
+ -webkit-tap-highlight-color: transparent;
+}
+.leaflet-container a {
+ -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
+}
+.leaflet-tile {
+ filter: inherit;
+ visibility: hidden;
+ }
+.leaflet-tile-loaded {
+ visibility: inherit;
+ }
+.leaflet-zoom-box {
+ width: 0;
+ height: 0;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ z-index: 800;
+ }
+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
+.leaflet-overlay-pane svg {
+ -moz-user-select: none;
+ }
+
+.leaflet-pane { z-index: 400; }
+
+.leaflet-tile-pane { z-index: 200; }
+.leaflet-overlay-pane { z-index: 400; }
+.leaflet-shadow-pane { z-index: 500; }
+.leaflet-marker-pane { z-index: 600; }
+.leaflet-tooltip-pane { z-index: 650; }
+.leaflet-popup-pane { z-index: 700; }
+
+.leaflet-map-pane canvas { z-index: 100; }
+.leaflet-map-pane svg { z-index: 200; }
+
+.leaflet-vml-shape {
+ width: 1px;
+ height: 1px;
+ }
+.lvml {
+ behavior: url(#default#VML);
+ display: inline-block;
+ position: absolute;
+ }
+
+
+/* control positioning */
+
+.leaflet-control {
+ position: relative;
+ z-index: 800;
+ pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+ pointer-events: auto;
+ }
+.leaflet-top,
+.leaflet-bottom {
+ position: absolute;
+ z-index: 1000;
+ pointer-events: none;
+ }
+.leaflet-top {
+ top: 0;
+ }
+.leaflet-right {
+ right: 0;
+ }
+.leaflet-bottom {
+ bottom: 0;
+ }
+.leaflet-left {
+ left: 0;
+ }
+.leaflet-control {
+ float: left;
+ clear: both;
+ }
+.leaflet-right .leaflet-control {
+ float: right;
+ }
+.leaflet-top .leaflet-control {
+ margin-top: 10px;
+ }
+.leaflet-bottom .leaflet-control {
+ margin-bottom: 10px;
+ }
+.leaflet-left .leaflet-control {
+ margin-left: 10px;
+ }
+.leaflet-right .leaflet-control {
+ margin-right: 10px;
+ }
+
+
+/* zoom and fade animations */
+
+.leaflet-fade-anim .leaflet-tile {
+ will-change: opacity;
+ }
+.leaflet-fade-anim .leaflet-popup {
+ opacity: 0;
+ -webkit-transition: opacity 0.2s linear;
+ -moz-transition: opacity 0.2s linear;
+ transition: opacity 0.2s linear;
+ }
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
+ opacity: 1;
+ }
+.leaflet-zoom-animated {
+ -webkit-transform-origin: 0 0;
+ -ms-transform-origin: 0 0;
+ transform-origin: 0 0;
+ }
+.leaflet-zoom-anim .leaflet-zoom-animated {
+ will-change: transform;
+ }
+.leaflet-zoom-anim .leaflet-zoom-animated {
+ -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
+ -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
+ transition: transform 0.25s cubic-bezier(0,0,0.25,1);
+ }
+.leaflet-zoom-anim .leaflet-tile,
+.leaflet-pan-anim .leaflet-tile {
+ -webkit-transition: none;
+ -moz-transition: none;
+ transition: none;
+ }
+
+.leaflet-zoom-anim .leaflet-zoom-hide {
+ visibility: hidden;
+ }
+
+
+/* cursors */
+
+.leaflet-interactive {
+ cursor: pointer;
+ }
+.leaflet-grab {
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+ cursor: grab;
+ }
+.leaflet-crosshair,
+.leaflet-crosshair .leaflet-interactive {
+ cursor: crosshair;
+ }
+.leaflet-popup-pane,
+.leaflet-control {
+ cursor: auto;
+ }
+.leaflet-dragging .leaflet-grab,
+.leaflet-dragging .leaflet-grab .leaflet-interactive,
+.leaflet-dragging .leaflet-marker-draggable {
+ cursor: move;
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ cursor: grabbing;
+ }
+
+/* marker & overlays interactivity */
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-image-layer,
+.leaflet-pane > svg path,
+.leaflet-tile-container {
+ pointer-events: none;
+ }
+
+.leaflet-marker-icon.leaflet-interactive,
+.leaflet-image-layer.leaflet-interactive,
+.leaflet-pane > svg path.leaflet-interactive,
+svg.leaflet-image-layer.leaflet-interactive path {
+ pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+ pointer-events: auto;
+ }
+
+/* visual tweaks */
+
+.leaflet-container {
+ background: #ddd;
+ outline: 0;
+ }
+.leaflet-container a {
+ color: #0078A8;
+ }
+.leaflet-container a.leaflet-active {
+ outline: 2px solid orange;
+ }
+.leaflet-zoom-box {
+ border: 2px dotted #38f;
+ background: rgba(255,255,255,0.5);
+ }
+
+
+/* general typography */
+.leaflet-container {
+ font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
+ }
+
+
+/* general toolbar styles */
+
+.leaflet-bar {
+ box-shadow: 0 1px 5px rgba(0,0,0,0.65);
+ border-radius: 4px;
+ }
+.leaflet-bar a,
+.leaflet-bar a:hover {
+ background-color: #fff;
+ border-bottom: 1px solid #ccc;
+ width: 26px;
+ height: 26px;
+ line-height: 26px;
+ display: block;
+ text-align: center;
+ text-decoration: none;
+ color: black;
+ }
+.leaflet-bar a,
+.leaflet-control-layers-toggle {
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+ display: block;
+ }
+.leaflet-bar a:hover {
+ background-color: #f4f4f4;
+ }
+.leaflet-bar a:first-child {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ }
+.leaflet-bar a:last-child {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-bottom: none;
+ }
+.leaflet-bar a.leaflet-disabled {
+ cursor: default;
+ background-color: #f4f4f4;
+ color: #bbb;
+ }
+
+.leaflet-touch .leaflet-bar a {
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ }
+.leaflet-touch .leaflet-bar a:first-child {
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ }
+.leaflet-touch .leaflet-bar a:last-child {
+ border-bottom-left-radius: 2px;
+ border-bottom-right-radius: 2px;
+ }
+
+/* zoom control */
+
+.leaflet-control-zoom-in,
+.leaflet-control-zoom-out {
+ font: bold 18px 'Lucida Console', Monaco, monospace;
+ text-indent: 1px;
+ }
+
+.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
+ font-size: 22px;
+ }
+
+
+/* layers control */
+
+.leaflet-control-layers {
+ box-shadow: 0 1px 5px rgba(0,0,0,0.4);
+ background: #fff;
+ border-radius: 5px;
+ }
+.leaflet-control-layers-toggle {
+ background-image: url(images/layers.png);
+ width: 36px;
+ height: 36px;
+ }
+.leaflet-retina .leaflet-control-layers-toggle {
+ background-image: url(images/layers-2x.png);
+ background-size: 26px 26px;
+ }
+.leaflet-touch .leaflet-control-layers-toggle {
+ width: 44px;
+ height: 44px;
+ }
+.leaflet-control-layers .leaflet-control-layers-list,
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
+ display: none;
+ }
+.leaflet-control-layers-expanded .leaflet-control-layers-list {
+ display: block;
+ position: relative;
+ }
+.leaflet-control-layers-expanded {
+ padding: 6px 10px 6px 6px;
+ color: #333;
+ background: #fff;
+ }
+.leaflet-control-layers-scrollbar {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ padding-right: 5px;
+ }
+.leaflet-control-layers-selector {
+ margin-top: 2px;
+ position: relative;
+ top: 1px;
+ }
+.leaflet-control-layers label {
+ display: block;
+ }
+.leaflet-control-layers-separator {
+ height: 0;
+ border-top: 1px solid #ddd;
+ margin: 5px -10px 5px -6px;
+ }
+
+/* Default icon URLs */
+.leaflet-default-icon-path {
+ background-image: url(images/marker-icon.png);
+ }
+
+
+/* attribution and scale controls */
+
+.leaflet-container .leaflet-control-attribution {
+ background: #fff;
+ background: rgba(255, 255, 255, 0.7);
+ margin: 0;
+ }
+.leaflet-control-attribution,
+.leaflet-control-scale-line {
+ padding: 0 5px;
+ color: #333;
+ }
+.leaflet-control-attribution a {
+ text-decoration: none;
+ }
+.leaflet-control-attribution a:hover {
+ text-decoration: underline;
+ }
+.leaflet-container .leaflet-control-attribution,
+.leaflet-container .leaflet-control-scale {
+ font-size: 11px;
+ }
+.leaflet-left .leaflet-control-scale {
+ margin-left: 5px;
+ }
+.leaflet-bottom .leaflet-control-scale {
+ margin-bottom: 5px;
+ }
+.leaflet-control-scale-line {
+ border: 2px solid #777;
+ border-top: none;
+ line-height: 1.1;
+ padding: 2px 5px 1px;
+ font-size: 11px;
+ white-space: nowrap;
+ overflow: hidden;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+
+ background: #fff;
+ background: rgba(255, 255, 255, 0.5);
+ }
+.leaflet-control-scale-line:not(:first-child) {
+ border-top: 2px solid #777;
+ border-bottom: none;
+ margin-top: -2px;
+ }
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {
+ border-bottom: 2px solid #777;
+ }
+
+.leaflet-touch .leaflet-control-attribution,
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+ box-shadow: none;
+ }
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+ border: 2px solid rgba(0,0,0,0.2);
+ background-clip: padding-box;
+ }
+
+
+/* popup */
+
+.leaflet-popup {
+ position: absolute;
+ text-align: center;
+ margin-bottom: 20px;
+ }
+.leaflet-popup-content-wrapper {
+ padding: 1px;
+ text-align: left;
+ border-radius: 12px;
+ }
+.leaflet-popup-content {
+ margin: 13px 19px;
+ line-height: 1.4;
+ }
+.leaflet-popup-content p {
+ margin: 18px 0;
+ }
+.leaflet-popup-tip-container {
+ width: 40px;
+ height: 20px;
+ position: absolute;
+ left: 50%;
+ margin-left: -20px;
+ overflow: hidden;
+ pointer-events: none;
+ }
+.leaflet-popup-tip {
+ width: 17px;
+ height: 17px;
+ padding: 1px;
+
+ margin: -10px auto 0;
+
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+.leaflet-popup-content-wrapper,
+.leaflet-popup-tip {
+ background: white;
+ color: #333;
+ box-shadow: 0 3px 14px rgba(0,0,0,0.4);
+ }
+.leaflet-container a.leaflet-popup-close-button {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 4px 4px 0 0;
+ border: none;
+ text-align: center;
+ width: 18px;
+ height: 14px;
+ font: 16px/14px Tahoma, Verdana, sans-serif;
+ color: #c3c3c3;
+ text-decoration: none;
+ font-weight: bold;
+ background: transparent;
+ }
+.leaflet-container a.leaflet-popup-close-button:hover {
+ color: #999;
+ }
+.leaflet-popup-scrolled {
+ overflow: auto;
+ border-bottom: 1px solid #ddd;
+ border-top: 1px solid #ddd;
+ }
+
+.leaflet-oldie .leaflet-popup-content-wrapper {
+ zoom: 1;
+ }
+.leaflet-oldie .leaflet-popup-tip {
+ width: 24px;
+ margin: 0 auto;
+
+ -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
+ filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
+ }
+.leaflet-oldie .leaflet-popup-tip-container {
+ margin-top: -1px;
+ }
+
+.leaflet-oldie .leaflet-control-zoom,
+.leaflet-oldie .leaflet-control-layers,
+.leaflet-oldie .leaflet-popup-content-wrapper,
+.leaflet-oldie .leaflet-popup-tip {
+ border: 1px solid #999;
+ }
+
+
+/* div icon */
+
+.leaflet-div-icon {
+ background: #fff;
+ border: 1px solid #666;
+ }
+
+
+/* Tooltip */
+/* Base styles for the element that has a tooltip */
+.leaflet-tooltip {
+ position: absolute;
+ padding: 6px;
+ background-color: #fff;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ color: #222;
+ white-space: nowrap;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ pointer-events: none;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.4);
+ }
+.leaflet-tooltip.leaflet-clickable {
+ cursor: pointer;
+ pointer-events: auto;
+ }
+.leaflet-tooltip-top:before,
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+ position: absolute;
+ pointer-events: none;
+ border: 6px solid transparent;
+ background: transparent;
+ content: "";
+ }
+
+/* Directions */
+
+.leaflet-tooltip-bottom {
+ margin-top: 6px;
+}
+.leaflet-tooltip-top {
+ margin-top: -6px;
+}
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-top:before {
+ left: 50%;
+ margin-left: -6px;
+ }
+.leaflet-tooltip-top:before {
+ bottom: 0;
+ margin-bottom: -12px;
+ border-top-color: #fff;
+ }
+.leaflet-tooltip-bottom:before {
+ top: 0;
+ margin-top: -12px;
+ margin-left: -6px;
+ border-bottom-color: #fff;
+ }
+.leaflet-tooltip-left {
+ margin-left: -6px;
+}
+.leaflet-tooltip-right {
+ margin-left: 6px;
+}
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+ top: 50%;
+ margin-top: -6px;
+ }
+.leaflet-tooltip-left:before {
+ right: 0;
+ margin-right: -12px;
+ border-left-color: #fff;
+ }
+.leaflet-tooltip-right:before {
+ left: 0;
+ margin-left: -12px;
+ border-right-color: #fff;
+ }
diff --git a/public/static/leaflet/leaflet.js b/public/static/leaflet/leaflet.js
new file mode 100644
index 0000000..9660fdb
--- /dev/null
+++ b/public/static/leaflet/leaflet.js
@@ -0,0 +1,5 @@
+/* @preserve
+ * Leaflet 1.5.1+build.2e3e0ff, a JS library for interactive maps. http://leafletjs.com
+ * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
+ */
+!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i(t.L={})}(this,function(t){"use strict";var i=Object.freeze;function h(t){var i,e,n,o;for(e=1,n=arguments.length;e<n;e++)for(i in o=arguments[e])t[i]=o[i];return t}Object.freeze=function(t){return t};var s=Object.create||function(t){return e.prototype=t,new e};function e(){}function a(t,i){var e=Array.prototype.slice;if(t.bind)return t.bind.apply(t,e.call(arguments,1));var n=e.call(arguments,2);return function(){return t.apply(i,n.length?n.concat(e.call(arguments)):arguments)}}var n=0;function u(t){return t._leaflet_id=t._leaflet_id||++n,t._leaflet_id}function o(t,i,e){var n,o,s,r;return r=function(){n=!1,o&&(s.apply(e,o),o=!1)},s=function(){n?o=arguments:(t.apply(e,arguments),setTimeout(r,i),n=!0)}}function r(t,i,e){var n=i[1],o=i[0],s=n-o;return t===n&&e?t:((t-o)%s+s)%s+o}function l(){return!1}function c(t,i){return i=void 0===i?6:i,+(Math.round(t+"e+"+i)+"e-"+i)}function _(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function d(t){return _(t).split(/\s+/)}function p(t,i){for(var e in t.hasOwnProperty("options")||(t.options=t.options?s(t.options):{}),i)t.options[e]=i[e];return t.options}function m(t,i,e){var n=[];for(var o in t)n.push(encodeURIComponent(e?o.toUpperCase():o)+"="+encodeURIComponent(t[o]));return(i&&-1!==i.indexOf("?")?"&":"?")+n.join("&")}var f=/\{ *([\w_-]+) *\}/g;function g(t,n){return t.replace(f,function(t,i){var e=n[i];if(void 0===e)throw new Error("No value provided for variable "+t);return"function"==typeof e&&(e=e(n)),e})}var v=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)};function y(t,i){for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1}var x="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";function w(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}var P=0;function b(t){var i=+new Date,e=Math.max(0,16-(i-P));return P=i+e,window.setTimeout(t,e)}var T=window.requestAnimationFrame||w("RequestAnimationFrame")||b,z=window.cancelAnimationFrame||w("CancelAnimationFrame")||w("CancelRequestAnimationFrame")||function(t){window.clearTimeout(t)};function M(t,i,e){if(!e||T!==b)return T.call(window,a(t,i));t.call(i)}function C(t){t&&z.call(window,t)}var S=(Object.freeze||Object)({freeze:i,extend:h,create:s,bind:a,lastId:n,stamp:u,throttle:o,wrapNum:r,falseFn:l,formatNum:c,trim:_,splitWords:d,setOptions:p,getParamString:m,template:g,isArray:v,indexOf:y,emptyImageUrl:x,requestFn:T,cancelFn:z,requestAnimFrame:M,cancelAnimFrame:C});function Z(){}Z.extend=function(t){function i(){this.initialize&&this.initialize.apply(this,arguments),this.callInitHooks()}var e=i.__super__=this.prototype,n=s(e);for(var o in(n.constructor=i).prototype=n,this)this.hasOwnProperty(o)&&"prototype"!==o&&"__super__"!==o&&(i[o]=this[o]);return t.statics&&(h(i,t.statics),delete t.statics),t.includes&&(function(t){if("undefined"==typeof L||!L||!L.Mixin)return;t=v(t)?t:[t];for(var i=0;i<t.length;i++)t[i]===L.Mixin.Events&&console.warn("Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead.",(new Error).stack)}(t.includes),h.apply(null,[n].concat(t.includes)),delete t.includes),n.options&&(t.options=h(s(n.options),t.options)),h(n,t),n._initHooks=[],n.callInitHooks=function(){if(!this._initHooksCalled){e.callInitHooks&&e.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,i=n._initHooks.length;t<i;t++)n._initHooks[t].call(this)}},i},Z.include=function(t){return h(this.prototype,t),this},Z.mergeOptions=function(t){return h(this.prototype.options,t),this},Z.addInitHook=function(t){var i=Array.prototype.slice.call(arguments,1),e="function"==typeof t?t:function(){this[t].apply(this,i)};return this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(e),this};var E={on:function(t,i,e){if("object"==typeof t)for(var n in t)this._on(n,t[n],i);else for(var o=0,s=(t=d(t)).length;o<s;o++)this._on(t[o],i,e);return this},off:function(t,i,e){if(t)if("object"==typeof t)for(var n in t)this._off(n,t[n],i);else for(var o=0,s=(t=d(t)).length;o<s;o++)this._off(t[o],i,e);else delete this._events;return this},_on:function(t,i,e){this._events=this._events||{};var n=this._events[t];n||(n=[],this._events[t]=n),e===this&&(e=void 0);for(var o={fn:i,ctx:e},s=n,r=0,a=s.length;r<a;r++)if(s[r].fn===i&&s[r].ctx===e)return;s.push(o)},_off:function(t,i,e){var n,o,s;if(this._events&&(n=this._events[t]))if(i){if(e===this&&(e=void 0),n)for(o=0,s=n.length;o<s;o++){var r=n[o];if(r.ctx===e&&r.fn===i)return r.fn=l,this._firingCount&&(this._events[t]=n=n.slice()),void n.splice(o,1)}}else{for(o=0,s=n.length;o<s;o++)n[o].fn=l;delete this._events[t]}},fire:function(t,i,e){if(!this.listens(t,e))return this;var n=h({},i,{type:t,target:this,sourceTarget:i&&i.sourceTarget||this});if(this._events){var o=this._events[t];if(o){this._firingCount=this._firingCount+1||1;for(var s=0,r=o.length;s<r;s++){var a=o[s];a.fn.call(a.ctx||this,n)}this._firingCount--}}return e&&this._propagateEvent(n),this},listens:function(t,i){var e=this._events&&this._events[t];if(e&&e.length)return!0;if(i)for(var n in this._eventParents)if(this._eventParents[n].listens(t,i))return!0;return!1},once:function(t,i,e){if("object"==typeof t){for(var n in t)this.once(n,t[n],i);return this}var o=a(function(){this.off(t,i,e).off(t,o,e)},this);return this.on(t,i,e).on(t,o,e)},addEventParent:function(t){return this._eventParents=this._eventParents||{},this._eventParents[u(t)]=t,this},removeEventParent:function(t){return this._eventParents&&delete this._eventParents[u(t)],this},_propagateEvent:function(t){for(var i in this._eventParents)this._eventParents[i].fire(t.type,h({layer:t.target,propagatedFrom:t.target},t),!0)}};E.addEventListener=E.on,E.removeEventListener=E.clearAllEventListeners=E.off,E.addOneTimeEventListener=E.once,E.fireEvent=E.fire,E.hasEventListeners=E.listens;var k=Z.extend(E);function B(t,i,e){this.x=e?Math.round(t):t,this.y=e?Math.round(i):i}var A=Math.trunc||function(t){return 0<t?Math.floor(t):Math.ceil(t)};function I(t,i,e){return t instanceof B?t:v(t)?new B(t[0],t[1]):null==t?t:"object"==typeof t&&"x"in t&&"y"in t?new B(t.x,t.y):new B(t,i,e)}function O(t,i){if(t)for(var e=i?[t,i]:t,n=0,o=e.length;n<o;n++)this.extend(e[n])}function R(t,i){return!t||t instanceof O?t:new O(t,i)}function N(t,i){if(t)for(var e=i?[t,i]:t,n=0,o=e.length;n<o;n++)this.extend(e[n])}function D(t,i){return t instanceof N?t:new N(t,i)}function j(t,i,e){if(isNaN(t)||isNaN(i))throw new Error("Invalid LatLng object: ("+t+", "+i+")");this.lat=+t,this.lng=+i,void 0!==e&&(this.alt=+e)}function W(t,i,e){return t instanceof j?t:v(t)&&"object"!=typeof t[0]?3===t.length?new j(t[0],t[1],t[2]):2===t.length?new j(t[0],t[1]):null:null==t?t:"object"==typeof t&&"lat"in t?new j(t.lat,"lng"in t?t.lng:t.lon,t.alt):void 0===i?null:new j(t,i,e)}B.prototype={clone:function(){return new B(this.x,this.y)},add:function(t){return this.clone()._add(I(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(I(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},scaleBy:function(t){return new B(this.x*t.x,this.y*t.y)},unscaleBy:function(t){return new B(this.x/t.x,this.y/t.y)},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},ceil:function(){return this.clone()._ceil()},_ceil:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},trunc:function(){return this.clone()._trunc()},_trunc:function(){return this.x=A(this.x),this.y=A(this.y),this},distanceTo:function(t){var i=(t=I(t)).x-this.x,e=t.y-this.y;return Math.sqrt(i*i+e*e)},equals:function(t){return(t=I(t)).x===this.x&&t.y===this.y},contains:function(t){return t=I(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+c(this.x)+", "+c(this.y)+")"}},O.prototype={extend:function(t){return t=I(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new B((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new B(this.min.x,this.max.y)},getTopRight:function(){return new B(this.max.x,this.min.y)},getTopLeft:function(){return this.min},getBottomRight:function(){return this.max},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var i,e;return(t="number"==typeof t[0]||t instanceof B?I(t):R(t))instanceof O?(i=t.min,e=t.max):i=e=t,i.x>=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=R(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>=i.x&&n.x<=e.x,r=o.y>=i.y&&n.y<=e.y;return s&&r},overlaps:function(t){t=R(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>i.x&&n.x<e.x,r=o.y>i.y&&n.y<e.y;return s&&r},isValid:function(){return!(!this.min||!this.max)}},N.prototype={extend:function(t){var i,e,n=this._southWest,o=this._northEast;if(t instanceof j)e=i=t;else{if(!(t instanceof N))return t?this.extend(W(t)||D(t)):this;if(i=t._southWest,e=t._northEast,!i||!e)return this}return n||o?(n.lat=Math.min(i.lat,n.lat),n.lng=Math.min(i.lng,n.lng),o.lat=Math.max(e.lat,o.lat),o.lng=Math.max(e.lng,o.lng)):(this._southWest=new j(i.lat,i.lng),this._northEast=new j(e.lat,e.lng)),this},pad:function(t){var i=this._southWest,e=this._northEast,n=Math.abs(i.lat-e.lat)*t,o=Math.abs(i.lng-e.lng)*t;return new N(new j(i.lat-n,i.lng-o),new j(e.lat+n,e.lng+o))},getCenter:function(){return new j((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new j(this.getNorth(),this.getWest())},getSouthEast:function(){return new j(this.getSouth(),this.getEast())},getWest:function(){return this._southWest.lng},getSouth:function(){return this._southWest.lat},getEast:function(){return this._northEast.lng},getNorth:function(){return this._northEast.lat},contains:function(t){t="number"==typeof t[0]||t instanceof j||"lat"in t?W(t):D(t);var i,e,n=this._southWest,o=this._northEast;return t instanceof N?(i=t.getSouthWest(),e=t.getNorthEast()):i=e=t,i.lat>=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=D(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>=i.lat&&n.lat<=e.lat,r=o.lng>=i.lng&&n.lng<=e.lng;return s&&r},overlaps:function(t){t=D(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>i.lat&&n.lat<e.lat,r=o.lng>i.lng&&n.lng<e.lng;return s&&r},toBBoxString:function(){return[this.getWest(),this.getSouth(),this.getEast(),this.getNorth()].join(",")},equals:function(t,i){return!!t&&(t=D(t),this._southWest.equals(t.getSouthWest(),i)&&this._northEast.equals(t.getNorthEast(),i))},isValid:function(){return!(!this._southWest||!this._northEast)}};var H,F={latLngToPoint:function(t,i){var e=this.projection.project(t),n=this.scale(i);return this.transformation._transform(e,n)},pointToLatLng:function(t,i){var e=this.scale(i),n=this.transformation.untransform(t,e);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},unproject:function(t){return this.projection.unproject(t)},scale:function(t){return 256*Math.pow(2,t)},zoom:function(t){return Math.log(t/256)/Math.LN2},getProjectedBounds:function(t){if(this.infinite)return null;var i=this.projection.bounds,e=this.scale(t);return new O(this.transformation.transform(i.min,e),this.transformation.transform(i.max,e))},infinite:!(j.prototype={equals:function(t,i){return!!t&&(t=W(t),Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng))<=(void 0===i?1e-9:i))},toString:function(t){return"LatLng("+c(this.lat,t)+", "+c(this.lng,t)+")"},distanceTo:function(t){return U.distance(this,W(t))},wrap:function(){return U.wrapLatLng(this)},toBounds:function(t){var i=180*t/40075017,e=i/Math.cos(Math.PI/180*this.lat);return D([this.lat-i,this.lng-e],[this.lat+i,this.lng+e])},clone:function(){return new j(this.lat,this.lng,this.alt)}}),wrapLatLng:function(t){var i=this.wrapLng?r(t.lng,this.wrapLng,!0):t.lng;return new j(this.wrapLat?r(t.lat,this.wrapLat,!0):t.lat,i,t.alt)},wrapLatLngBounds:function(t){var i=t.getCenter(),e=this.wrapLatLng(i),n=i.lat-e.lat,o=i.lng-e.lng;if(0==n&&0==o)return t;var s=t.getSouthWest(),r=t.getNorthEast();return new N(new j(s.lat-n,s.lng-o),new j(r.lat-n,r.lng-o))}},U=h({},F,{wrapLng:[-180,180],R:6371e3,distance:function(t,i){var e=Math.PI/180,n=t.lat*e,o=i.lat*e,s=Math.sin((i.lat-t.lat)*e/2),r=Math.sin((i.lng-t.lng)*e/2),a=s*s+Math.cos(n)*Math.cos(o)*r*r,h=2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));return this.R*h}}),V=6378137,q={R:V,MAX_LATITUDE:85.0511287798,project:function(t){var i=Math.PI/180,e=this.MAX_LATITUDE,n=Math.max(Math.min(e,t.lat),-e),o=Math.sin(n*i);return new B(this.R*t.lng*i,this.R*Math.log((1+o)/(1-o))/2)},unproject:function(t){var i=180/Math.PI;return new j((2*Math.atan(Math.exp(t.y/this.R))-Math.PI/2)*i,t.x*i/this.R)},bounds:(H=V*Math.PI,new O([-H,-H],[H,H]))};function G(t,i,e,n){if(v(t))return this._a=t[0],this._b=t[1],this._c=t[2],void(this._d=t[3]);this._a=t,this._b=i,this._c=e,this._d=n}function K(t,i,e,n){return new G(t,i,e,n)}G.prototype={transform:function(t,i){return this._transform(t.clone(),i)},_transform:function(t,i){return i=i||1,t.x=i*(this._a*t.x+this._b),t.y=i*(this._c*t.y+this._d),t},untransform:function(t,i){return i=i||1,new B((t.x/i-this._b)/this._a,(t.y/i-this._d)/this._c)}};var Y,X=h({},U,{code:"EPSG:3857",projection:q,transformation:(Y=.5/(Math.PI*q.R),K(Y,.5,-Y,.5))}),J=h({},X,{code:"EPSG:900913"});function $(t){return document.createElementNS("http://www.w3.org/2000/svg",t)}function Q(t,i){var e,n,o,s,r,a,h="";for(e=0,o=t.length;e<o;e++){for(n=0,s=(r=t[e]).length;n<s;n++)h+=(n?"L":"M")+(a=r[n]).x+" "+a.y;h+=i?Zt?"z":"x":""}return h||"M0 0"}var tt=document.documentElement.style,it="ActiveXObject"in window,et=it&&!document.addEventListener,nt="msLaunchUri"in navigator&&!("documentMode"in document),ot=kt("webkit"),st=kt("android"),rt=kt("android 2")||kt("android 3"),at=parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1],10),ht=st&&kt("Google")&&at<537&&!("AudioNode"in window),ut=!!window.opera,lt=kt("chrome"),ct=kt("gecko")&&!ot&&!ut&&!it,_t=!lt&&kt("safari"),dt=kt("phantom"),pt="OTransition"in tt,mt=0===navigator.platform.indexOf("Win"),ft=it&&"transition"in tt,gt="WebKitCSSMatrix"in window&&"m11"in new window.WebKitCSSMatrix&&!rt,vt="MozPerspective"in tt,yt=!window.L_DISABLE_3D&&(ft||gt||vt)&&!pt&&!dt,xt="undefined"!=typeof orientation||kt("mobile"),wt=xt&&ot,Pt=xt&&gt,bt=!window.PointerEvent&&window.MSPointerEvent,Lt=!(!window.PointerEvent&&!bt),Tt=!window.L_NO_TOUCH&&(Lt||"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch),zt=xt&&ut,Mt=xt&&ct,Ct=1<(window.devicePixelRatio||window.screen.deviceXDPI/window.screen.logicalXDPI),St=!!document.createElement("canvas").getContext,Zt=!(!document.createElementNS||!$("svg").createSVGRect),Et=!Zt&&function(){try{var t=document.createElement("div");t.innerHTML='<v:shape adj="1"/>';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}();function kt(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var Bt=(Object.freeze||Object)({ie:it,ielt9:et,edge:nt,webkit:ot,android:st,android23:rt,androidStock:ht,opera:ut,chrome:lt,gecko:ct,safari:_t,phantom:dt,opera12:pt,win:mt,ie3d:ft,webkit3d:gt,gecko3d:vt,any3d:yt,mobile:xt,mobileWebkit:wt,mobileWebkit3d:Pt,msPointer:bt,pointer:Lt,touch:Tt,mobileOpera:zt,mobileGecko:Mt,retina:Ct,canvas:St,svg:Zt,vml:Et}),At=bt?"MSPointerDown":"pointerdown",It=bt?"MSPointerMove":"pointermove",Ot=bt?"MSPointerUp":"pointerup",Rt=bt?"MSPointerCancel":"pointercancel",Nt=["INPUT","SELECT","OPTION"],Dt={},jt=!1,Wt=0;function Ht(t,i,e,n){return"touchstart"===i?function(t,i,e){var n=a(function(t){if("mouse"!==t.pointerType&&t.MSPOINTER_TYPE_MOUSE&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(Nt.indexOf(t.target.tagName)<0))return;Di(t)}qt(t,i)});t["_leaflet_touchstart"+e]=n,t.addEventListener(At,n,!1),jt||(document.documentElement.addEventListener(At,Ft,!0),document.documentElement.addEventListener(It,Ut,!0),document.documentElement.addEventListener(Ot,Vt,!0),document.documentElement.addEventListener(Rt,Vt,!0),jt=!0)}(t,e,n):"touchmove"===i?function(t,i,e){var n=function(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons)&&qt(t,i)};t["_leaflet_touchmove"+e]=n,t.addEventListener(It,n,!1)}(t,e,n):"touchend"===i&&function(t,i,e){var n=function(t){qt(t,i)};t["_leaflet_touchend"+e]=n,t.addEventListener(Ot,n,!1),t.addEventListener(Rt,n,!1)}(t,e,n),this}function Ft(t){Dt[t.pointerId]=t,Wt++}function Ut(t){Dt[t.pointerId]&&(Dt[t.pointerId]=t)}function Vt(t){delete Dt[t.pointerId],Wt--}function qt(t,i){for(var e in t.touches=[],Dt)t.touches.push(Dt[e]);t.changedTouches=[t],i(t)}var Gt=bt?"MSPointerDown":Lt?"pointerdown":"touchstart",Kt=bt?"MSPointerUp":Lt?"pointerup":"touchend",Yt="_leaflet_";function Xt(t,o,i){var s,r,a=!1;function e(t){var i;if(Lt){if(!nt||"mouse"===t.pointerType)return;i=Wt}else i=t.touches.length;if(!(1<i)){var e=Date.now(),n=e-(s||e);r=t.touches?t.touches[0]:t,a=0<n&&n<=250,s=e}}function n(t){if(a&&!r.cancelBubble){if(Lt){if(!nt||"mouse"===t.pointerType)return;var i,e,n={};for(e in r)i=r[e],n[e]=i&&i.bind?i.bind(r):i;r=n}r.type="dblclick",r.button=0,o(r),s=null}}return t[Yt+Gt+i]=e,t[Yt+Kt+i]=n,t[Yt+"dblclick"+i]=o,t.addEventListener(Gt,e,!1),t.addEventListener(Kt,n,!1),t.addEventListener("dblclick",o,!1),this}function Jt(t,i){var e=t[Yt+Gt+i],n=t[Yt+Kt+i],o=t[Yt+"dblclick"+i];return t.removeEventListener(Gt,e,!1),t.removeEventListener(Kt,n,!1),nt||t.removeEventListener("dblclick",o,!1),this}var $t,Qt,ti,ii,ei,ni=yi(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),oi=yi(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),si="webkitTransition"===oi||"OTransition"===oi?oi+"End":"transitionend";function ri(t){return"string"==typeof t?document.getElementById(t):t}function ai(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];if((!e||"auto"===e)&&document.defaultView){var n=document.defaultView.getComputedStyle(t,null);e=n?n[i]:null}return"auto"===e?null:e}function hi(t,i,e){var n=document.createElement(t);return n.className=i||"",e&&e.appendChild(n),n}function ui(t){var i=t.parentNode;i&&i.removeChild(t)}function li(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function ci(t){var i=t.parentNode;i&&i.lastChild!==t&&i.appendChild(t)}function _i(t){var i=t.parentNode;i&&i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function di(t,i){if(void 0!==t.classList)return t.classList.contains(i);var e=gi(t);return 0<e.length&&new RegExp("(^|\\s)"+i+"(\\s|$)").test(e)}function pi(t,i){if(void 0!==t.classList)for(var e=d(i),n=0,o=e.length;n<o;n++)t.classList.add(e[n]);else if(!di(t,i)){var s=gi(t);fi(t,(s?s+" ":"")+i)}}function mi(t,i){void 0!==t.classList?t.classList.remove(i):fi(t,_((" "+gi(t)+" ").replace(" "+i+" "," ")))}function fi(t,i){void 0===t.className.baseVal?t.className=i:t.className.baseVal=i}function gi(t){return t.correspondingElement&&(t=t.correspondingElement),void 0===t.className.baseVal?t.className:t.className.baseVal}function vi(t,i){"opacity"in t.style?t.style.opacity=i:"filter"in t.style&&function(t,i){var e=!1,n="DXImageTransform.Microsoft.Alpha";try{e=t.filters.item(n)}catch(t){if(1===i)return}i=Math.round(100*i),e?(e.Enabled=100!==i,e.Opacity=i):t.style.filter+=" progid:"+n+"(opacity="+i+")"}(t,i)}function yi(t){for(var i=document.documentElement.style,e=0;e<t.length;e++)if(t[e]in i)return t[e];return!1}function xi(t,i,e){var n=i||new B(0,0);t.style[ni]=(ft?"translate("+n.x+"px,"+n.y+"px)":"translate3d("+n.x+"px,"+n.y+"px,0)")+(e?" scale("+e+")":"")}function wi(t,i){t._leaflet_pos=i,yt?xi(t,i):(t.style.left=i.x+"px",t.style.top=i.y+"px")}function Pi(t){return t._leaflet_pos||new B(0,0)}if("onselectstart"in document)$t=function(){Ei(window,"selectstart",Di)},Qt=function(){Bi(window,"selectstart",Di)};else{var bi=yi(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]);$t=function(){if(bi){var t=document.documentElement.style;ti=t[bi],t[bi]="none"}},Qt=function(){bi&&(document.documentElement.style[bi]=ti,ti=void 0)}}function Li(){Ei(window,"dragstart",Di)}function Ti(){Bi(window,"dragstart",Di)}function zi(t){for(;-1===t.tabIndex;)t=t.parentNode;t.style&&(Mi(),ei=(ii=t).style.outline,t.style.outline="none",Ei(window,"keydown",Mi))}function Mi(){ii&&(ii.style.outline=ei,ei=ii=void 0,Bi(window,"keydown",Mi))}function Ci(t){for(;!((t=t.parentNode).offsetWidth&&t.offsetHeight||t===document.body););return t}function Si(t){var i=t.getBoundingClientRect();return{x:i.width/t.offsetWidth||1,y:i.height/t.offsetHeight||1,boundingClientRect:i}}var Zi=(Object.freeze||Object)({TRANSFORM:ni,TRANSITION:oi,TRANSITION_END:si,get:ri,getStyle:ai,create:hi,remove:ui,empty:li,toFront:ci,toBack:_i,hasClass:di,addClass:pi,removeClass:mi,setClass:fi,getClass:gi,setOpacity:vi,testProp:yi,setTransform:xi,setPosition:wi,getPosition:Pi,disableTextSelection:$t,enableTextSelection:Qt,disableImageDrag:Li,enableImageDrag:Ti,preventOutline:zi,restoreOutline:Mi,getSizedParentNode:Ci,getScale:Si});function Ei(t,i,e,n){if("object"==typeof i)for(var o in i)Ai(t,o,i[o],e);else for(var s=0,r=(i=d(i)).length;s<r;s++)Ai(t,i[s],e,n);return this}var ki="_leaflet_events";function Bi(t,i,e,n){if("object"==typeof i)for(var o in i)Ii(t,o,i[o],e);else if(i)for(var s=0,r=(i=d(i)).length;s<r;s++)Ii(t,i[s],e,n);else{for(var a in t[ki])Ii(t,a,t[ki][a]);delete t[ki]}return this}function Ai(i,t,e,n){var o=t+u(e)+(n?"_"+u(n):"");if(i[ki]&&i[ki][o])return this;var s=function(t){return e.call(n||i,t||window.event)},r=s;Lt&&0===t.indexOf("touch")?Ht(i,t,s,o):!Tt||"dblclick"!==t||Lt&&lt?"addEventListener"in i?"mousewheel"===t?i.addEventListener("onwheel"in i?"wheel":"mousewheel",s,!1):"mouseenter"===t||"mouseleave"===t?(s=function(t){t=t||window.event,Ki(i,t)&&r(t)},i.addEventListener("mouseenter"===t?"mouseover":"mouseout",s,!1)):("click"===t&&st&&(s=function(t){!function(t,i){var e=t.timeStamp||t.originalEvent&&t.originalEvent.timeStamp,n=Ui&&e-Ui;if(n&&100<n&&n<500||t.target._simulatedClick&&!t._simulated)return ji(t);Ui=e,i(t)}(t,r)}),i.addEventListener(t,s,!1)):"attachEvent"in i&&i.attachEvent("on"+t,s):Xt(i,s,o),i[ki]=i[ki]||{},i[ki][o]=s}function Ii(t,i,e,n){var o=i+u(e)+(n?"_"+u(n):""),s=t[ki]&&t[ki][o];if(!s)return this;Lt&&0===i.indexOf("touch")?function(t,i,e){var n=t["_leaflet_"+i+e];"touchstart"===i?t.removeEventListener(At,n,!1):"touchmove"===i?t.removeEventListener(It,n,!1):"touchend"===i&&(t.removeEventListener(Ot,n,!1),t.removeEventListener(Rt,n,!1))}(t,i,o):!Tt||"dblclick"!==i||Lt&&lt?"removeEventListener"in t?"mousewheel"===i?t.removeEventListener("onwheel"in t?"wheel":"mousewheel",s,!1):t.removeEventListener("mouseenter"===i?"mouseover":"mouseleave"===i?"mouseout":i,s,!1):"detachEvent"in t&&t.detachEvent("on"+i,s):Jt(t,o),t[ki][o]=null}function Oi(t){return t.stopPropagation?t.stopPropagation():t.originalEvent?t.originalEvent._stopped=!0:t.cancelBubble=!0,Gi(t),this}function Ri(t){return Ai(t,"mousewheel",Oi),this}function Ni(t){return Ei(t,"mousedown touchstart dblclick",Oi),Ai(t,"click",qi),this}function Di(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this}function ji(t){return Di(t),Oi(t),this}function Wi(t,i){if(!i)return new B(t.clientX,t.clientY);var e=Si(i),n=e.boundingClientRect;return new B((t.clientX-n.left)/e.x-i.clientLeft,(t.clientY-n.top)/e.y-i.clientTop)}var Hi=mt&&lt?2*window.devicePixelRatio:ct?window.devicePixelRatio:1;function Fi(t){return nt?t.wheelDeltaY/2:t.deltaY&&0===t.deltaMode?-t.deltaY/Hi:t.deltaY&&1===t.deltaMode?20*-t.deltaY:t.deltaY&&2===t.deltaMode?60*-t.deltaY:t.deltaX||t.deltaZ?0:t.wheelDelta?(t.wheelDeltaY||t.wheelDelta)/2:t.detail&&Math.abs(t.detail)<32765?20*-t.detail:t.detail?t.detail/-32765*60:0}var Ui,Vi={};function qi(t){Vi[t.type]=!0}function Gi(t){var i=Vi[t.type];return Vi[t.type]=!1,i}function Ki(t,i){var e=i.relatedTarget;if(!e)return!0;try{for(;e&&e!==t;)e=e.parentNode}catch(t){return!1}return e!==t}var Yi=(Object.freeze||Object)({on:Ei,off:Bi,stopPropagation:Oi,disableScrollPropagation:Ri,disableClickPropagation:Ni,preventDefault:Di,stop:ji,getMousePosition:Wi,getWheelDelta:Fi,fakeStop:qi,skipped:Gi,isExternalTarget:Ki,addListener:Ei,removeListener:Bi}),Xi=k.extend({run:function(t,i,e,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=e||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=Pi(t),this._offset=i.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=M(this._animate,this),this._step()},_step:function(t){var i=+new Date-this._startTime,e=1e3*this._duration;i<e?this._runFrame(this._easeOut(i/e),t):(this._runFrame(1),this._complete())},_runFrame:function(t,i){var e=this._startPos.add(this._offset.multiplyBy(t));i&&e._round(),wi(this._el,e),this.fire("step")},_complete:function(){C(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),Ji=k.extend({options:{crs:X,center:void 0,zoom:void 0,minZoom:void 0,maxZoom:void 0,layers:[],maxBounds:void 0,renderer:void 0,zoomAnimation:!0,zoomAnimationThreshold:4,fadeAnimation:!0,markerZoomAnimation:!0,transform3DLimit:8388608,zoomSnap:1,zoomDelta:1,trackResize:!0},initialize:function(t,i){i=p(this,i),this._handlers=[],this._layers={},this._zoomBoundLayers={},this._sizeChanged=!0,this._initContainer(t),this._initLayout(),this._onResize=a(this._onResize,this),this._initEvents(),i.maxBounds&&this.setMaxBounds(i.maxBounds),void 0!==i.zoom&&(this._zoom=this._limitZoom(i.zoom)),i.center&&void 0!==i.zoom&&this.setView(W(i.center),i.zoom,{reset:!0}),this.callInitHooks(),this._zoomAnimated=oi&&yt&&!zt&&this.options.zoomAnimation,this._zoomAnimated&&(this._createAnimProxy(),Ei(this._proxy,si,this._catchTransitionEnd,this)),this._addLayers(this.options.layers)},setView:function(t,i,e){if((i=void 0===i?this._zoom:this._limitZoom(i),t=this._limitCenter(W(t),i,this.options.maxBounds),e=e||{},this._stop(),this._loaded&&!e.reset&&!0!==e)&&(void 0!==e.animate&&(e.zoom=h({animate:e.animate},e.zoom),e.pan=h({animate:e.animate,duration:e.duration},e.pan)),this._zoom!==i?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,i,e.zoom):this._tryAnimatedPan(t,e.pan)))return clearTimeout(this._sizeTimer),this;return this._resetView(t,i),this},setZoom:function(t,i){return this._loaded?this.setView(this.getCenter(),t,{zoom:i}):(this._zoom=t,this)},zoomIn:function(t,i){return t=t||(yt?this.options.zoomDelta:1),this.setZoom(this._zoom+t,i)},zoomOut:function(t,i){return t=t||(yt?this.options.zoomDelta:1),this.setZoom(this._zoom-t,i)},setZoomAround:function(t,i,e){var n=this.getZoomScale(i),o=this.getSize().divideBy(2),s=(t instanceof B?t:this.latLngToContainerPoint(t)).subtract(o).multiplyBy(1-1/n),r=this.containerPointToLatLng(o.add(s));return this.setView(r,i,{zoom:e})},_getBoundsCenterZoom:function(t,i){i=i||{},t=t.getBounds?t.getBounds():D(t);var e=I(i.paddingTopLeft||i.padding||[0,0]),n=I(i.paddingBottomRight||i.padding||[0,0]),o=this.getBoundsZoom(t,!1,e.add(n));if((o="number"==typeof i.maxZoom?Math.min(i.maxZoom,o):o)===1/0)return{center:t.getCenter(),zoom:o};var s=n.subtract(e).divideBy(2),r=this.project(t.getSouthWest(),o),a=this.project(t.getNorthEast(),o);return{center:this.unproject(r.add(a).divideBy(2).add(s),o),zoom:o}},fitBounds:function(t,i){if(!(t=D(t)).isValid())throw new Error("Bounds are not valid.");var e=this._getBoundsCenterZoom(t,i);return this.setView(e.center,e.zoom,i)},fitWorld:function(t){return this.fitBounds([[-90,-180],[90,180]],t)},panTo:function(t,i){return this.setView(t,this._zoom,{pan:i})},panBy:function(t,i){if(i=i||{},!(t=I(t).round()).x&&!t.y)return this.fire("moveend");if(!0!==i.animate&&!this.getSize().contains(t))return this._resetView(this.unproject(this.project(this.getCenter()).add(t)),this.getZoom()),this;if(this._panAnim||(this._panAnim=new Xi,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),i.noMoveStart||this.fire("movestart"),!1!==i.animate){pi(this._mapPane,"leaflet-pan-anim");var e=this._getMapPanePos().subtract(t).round();this._panAnim.run(this._mapPane,e,i.duration||.25,i.easeLinearity)}else this._rawPanBy(t),this.fire("move").fire("moveend");return this},flyTo:function(n,o,t){if(!1===(t=t||{}).animate||!yt)return this.setView(n,o,t);this._stop();var s=this.project(this.getCenter()),r=this.project(n),i=this.getSize(),a=this._zoom;n=W(n),o=void 0===o?a:o;var h=Math.max(i.x,i.y),u=h*this.getZoomScale(a,o),l=r.distanceTo(s)||1,c=1.42,_=c*c;function e(t){var i=(u*u-h*h+(t?-1:1)*_*_*l*l)/(2*(t?u:h)*_*l),e=Math.sqrt(i*i+1)-i;return e<1e-9?-18:Math.log(e)}function d(t){return(Math.exp(t)-Math.exp(-t))/2}function p(t){return(Math.exp(t)+Math.exp(-t))/2}var m=e(0);function f(t){return h*(p(m)*function(t){return d(t)/p(t)}(m+c*t)-d(m))/_}var g=Date.now(),v=(e(1)-m)/c,y=t.duration?1e3*t.duration:1e3*v*.8;return this._moveStart(!0,t.noMoveStart),function t(){var i=(Date.now()-g)/y,e=function(t){return 1-Math.pow(1-t,1.5)}(i)*v;i<=1?(this._flyToFrame=M(t,this),this._move(this.unproject(s.add(r.subtract(s).multiplyBy(f(e)/l)),a),this.getScaleZoom(h/function(t){return h*(p(m)/p(m+c*t))}(e),a),{flyTo:!0})):this._move(n,o)._moveEnd(!0)}.call(this),this},flyToBounds:function(t,i){var e=this._getBoundsCenterZoom(t,i);return this.flyTo(e.center,e.zoom,i)},setMaxBounds:function(t){return(t=D(t)).isValid()?(this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this.options.maxBounds=t,this._loaded&&this._panInsideMaxBounds(),this.on("moveend",this._panInsideMaxBounds)):(this.options.maxBounds=null,this.off("moveend",this._panInsideMaxBounds))},setMinZoom:function(t){var i=this.options.minZoom;return this.options.minZoom=t,this._loaded&&i!==t&&(this.fire("zoomlevelschange"),this.getZoom()<this.options.minZoom)?this.setZoom(t):this},setMaxZoom:function(t){var i=this.options.maxZoom;return this.options.maxZoom=t,this._loaded&&i!==t&&(this.fire("zoomlevelschange"),this.getZoom()>this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),n=this._limitCenter(e,this._zoom,D(t));return e.equals(n)||this.panTo(n,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e=I((i=i||{}).paddingTopLeft||i.padding||[0,0]),n=I(i.paddingBottomRight||i.padding||[0,0]),o=this.getCenter(),s=this.project(o),r=this.project(t),a=this.getPixelBounds(),h=a.getSize().divideBy(2),u=R([a.min.add(e),a.max.subtract(n)]);if(!u.contains(r)){this._enforcingBounds=!0;var l=s.subtract(r),c=I(r.x+l.x,r.y+l.y);(r.x<u.min.x||r.x>u.max.x)&&(c.x=s.x-l.x,0<l.x?c.x+=h.x-e.x:c.x-=h.x-n.x),(r.y<u.min.y||r.y>u.max.y)&&(c.y=s.y-l.y,0<l.y?c.y+=h.y-e.y:c.y-=h.y-n.y),this.panTo(this.unproject(c),i),this._enforcingBounds=!1}return this},invalidateSize:function(t){if(!this._loaded)return this;t=h({animate:!1,pan:!0},!0===t?{animate:!0}:t);var i=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var e=this.getSize(),n=i.divideBy(2).round(),o=e.divideBy(2).round(),s=n.subtract(o);return s.x||s.y?(t.animate&&t.pan?this.panBy(s):(t.pan&&this._rawPanBy(s),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:i,newSize:e})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=h({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var i=a(this._handleGeolocationResponse,this),e=a(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(i,e,t):navigator.geolocation.getCurrentPosition(i,e,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i=t.code,e=t.message||(1===i?"permission denied":2===i?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:i,message:"Geolocation error: "+e+"."})},_handleGeolocationResponse:function(t){var i=new j(t.coords.latitude,t.coords.longitude),e=i.toBounds(2*t.coords.accuracy),n=this._locateOptions;if(n.setView){var o=this.getBoundsZoom(e);this.setView(i,n.maxZoom?Math.min(o,n.maxZoom):o)}var s={latlng:i,bounds:e,timestamp:t.timestamp};for(var r in t.coords)"number"==typeof t.coords[r]&&(s[r]=t.coords[r]);this.fire("locationfound",s)},addHandler:function(t,i){if(!i)return this;var e=this[t]=new i(this);return this._handlers.push(e),this.options[t]&&e.enable(),this},remove:function(){if(this._initEvents(!0),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}var t;for(t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),ui(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(C(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)ui(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){var e=hi("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i||this._mapPane);return t&&(this._panes[t]=e),e},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new N(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=D(t),e=I(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),a=t.getSouthEast(),h=this.getSize().subtract(e),u=R(this.project(a,n),this.project(r,n)).getSize(),l=yt?this.options.zoomSnap:1,c=h.x/u.x,_=h.y/u.y,d=i?Math.max(c,_):Math.min(c,_);return n=this.getScaleZoom(d,n),l&&(n=Math.round(n/(l/100))*(l/100),n=i?Math.ceil(n/l)*l:Math.floor(n/l)*l),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new B(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){var e=this._getTopLeftPoint(t,i);return new O(e,e.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs;i=void 0===i?this._zoom:i;var n=e.zoom(t*e.scale(i));return isNaN(n)?1/0:n},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(W(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(I(t),i)},layerPointToLatLng:function(t){var i=I(t).add(this.getPixelOrigin());return this.unproject(i)},latLngToLayerPoint:function(t){return this.project(W(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(W(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(D(t))},distance:function(t,i){return this.options.crs.distance(W(t),W(i))},containerPointToLayerPoint:function(t){return I(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return I(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var i=this.containerPointToLayerPoint(I(t));return this.layerPointToLatLng(i)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(W(t)))},mouseEventToContainerPoint:function(t){return Wi(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var i=this._container=ri(t);if(!i)throw new Error("Map container not found.");if(i._leaflet_id)throw new Error("Map container is already initialized.");Ei(i,"scroll",this._onScroll,this),this._containerId=u(i)},_initLayout:function(){var t=this._container;this._fadeAnimated=this.options.fadeAnimation&&yt,pi(t,"leaflet-container"+(Tt?" leaflet-touch":"")+(Ct?" leaflet-retina":"")+(et?" leaflet-oldie":"")+(_t?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var i=ai(t,"position");"absolute"!==i&&"relative"!==i&&"fixed"!==i&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),wi(this._mapPane,new B(0,0)),this.createPane("tilePane"),this.createPane("shadowPane"),this.createPane("overlayPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(pi(t.markerPane,"leaflet-zoom-hide"),pi(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,i){wi(this._mapPane,new B(0,0));var e=!this._loaded;this._loaded=!0,i=this._limitZoom(i),this.fire("viewprereset");var n=this._zoom!==i;this._moveStart(n,!1)._move(t,i)._moveEnd(n),this.fire("viewreset"),e&&this.fire("load")},_moveStart:function(t,i){return t&&this.fire("zoomstart"),i||this.fire("movestart"),this},_move:function(t,i,e){void 0===i&&(i=this._zoom);var n=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(n||e&&e.pinch)&&this.fire("zoom",e),this.fire("move",e)},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return C(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){wi(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={};var i=t?Bi:Ei;i((this._targets[u(this._container)]=this)._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&i(window,"resize",this._onResize,this),yt&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){C(this._resizeRequest),this._resizeRequest=M(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,n=[],o="mouseout"===i||"mouseover"===i,s=t.target||t.srcElement,r=!1;s;){if((e=this._targets[u(s)])&&("click"===i||"preclick"===i)&&!t._simulated&&this._draggableMoved(e)){r=!0;break}if(e&&e.listens(i,!0)){if(o&&!Ki(s,t))break;if(n.push(e),o)break}if(s===this._container)break;s=s.parentNode}return n.length||r||o||!Ki(s,t)||(n=[this]),n},_handleDOMEvent:function(t){if(this._loaded&&!Gi(t)){var i=t.type;"mousedown"!==i&&"keypress"!==i&&"keyup"!==i&&"keydown"!==i||zi(t.target||t.srcElement),this._fireDOMEvent(t,i)}},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,e){if("click"===t.type){var n=h({},t);n.type="preclick",this._fireDOMEvent(n,n.type,e)}if(!t._stopped&&(e=(e||[]).concat(this._findEventTargets(t,i))).length){var o=e[0];"contextmenu"===i&&o.listens(i,!0)&&Di(t);var s={originalEvent:t};if("keypress"!==t.type&&"keydown"!==t.type&&"keyup"!==t.type){var r=o.getLatLng&&(!o._radius||o._radius<=10);s.containerPoint=r?this.latLngToContainerPoint(o.getLatLng()):this.mouseEventToContainerPoint(t),s.layerPoint=this.containerPointToLayerPoint(s.containerPoint),s.latlng=r?o.getLatLng():this.layerPointToLatLng(s.layerPoint)}for(var a=0;a<e.length;a++)if(e[a].fire(i,s,!0),s.originalEvent._stopped||!1===e[a].options.bubblingMouseEvents&&-1!==y(this._mouseEvents,i))return}},_draggableMoved:function(t){return(t=t.dragging&&t.dragging.enabled()?t:this).dragging&&t.dragging.moved()||this.boxZoom&&this.boxZoom.moved()},_clearHandlers:function(){for(var t=0,i=this._handlers.length;t<i;t++)this._handlers[t].disable()},whenReady:function(t,i){return this._loaded?t.call(i||this,{target:this}):this.on("load",t,i),this},_getMapPanePos:function(){return Pi(this._mapPane)||new B(0,0)},_moved:function(){var t=this._getMapPanePos();return t&&!t.equals([0,0])},_getTopLeftPoint:function(t,i){return(t&&void 0!==i?this._getNewPixelOrigin(t,i):this.getPixelOrigin()).subtract(this._getMapPanePos())},_getNewPixelOrigin:function(t,i){var e=this.getSize()._divideBy(2);return this.project(t,i)._subtract(e)._add(this._getMapPanePos())._round()},_latLngToNewLayerPoint:function(t,i,e){var n=this._getNewPixelOrigin(e,i);return this.project(t,i)._subtract(n)},_latLngBoundsToNewLayerBounds:function(t,i,e){var n=this._getNewPixelOrigin(e,i);return R([this.project(t.getSouthWest(),i)._subtract(n),this.project(t.getNorthWest(),i)._subtract(n),this.project(t.getSouthEast(),i)._subtract(n),this.project(t.getNorthEast(),i)._subtract(n)])},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitCenter:function(t,i,e){if(!e)return t;var n=this.project(t,i),o=this.getSize().divideBy(2),s=new O(n.subtract(o),n.add(o)),r=this._getBoundsOffset(s,e,i);return r.round().equals([0,0])?t:this.unproject(n.add(r),i)},_limitOffset:function(t,i){if(!i)return t;var e=this.getPixelBounds(),n=new O(e.min.add(t),e.max.add(t));return t.add(this._getBoundsOffset(n,i))},_getBoundsOffset:function(t,i,e){var n=R(this.project(i.getNorthEast(),e),this.project(i.getSouthWest(),e)),o=n.min.subtract(t.min),s=n.max.subtract(t.max);return new B(this._rebound(o.x,-s.x),this._rebound(o.y,-s.y))},_rebound:function(t,i){return 0<t+i?Math.round(t-i)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(i))},_limitZoom:function(t){var i=this.getMinZoom(),e=this.getMaxZoom(),n=yt?this.options.zoomSnap:1;return n&&(t=Math.round(t/n)*n),Math.max(i,Math.min(e,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){mi(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,i){var e=this._getCenterOffset(t)._trunc();return!(!0!==(i&&i.animate)&&!this.getSize().contains(e))&&(this.panBy(e,i),!0)},_createAnimProxy:function(){var t=this._proxy=hi("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",function(t){var i=ni,e=this._proxy.style[i];xi(this._proxy,this.project(t.center,t.zoom),this.getZoomScale(t.zoom,1)),e===this._proxy.style[i]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on("load moveend",function(){var t=this.getCenter(),i=this.getZoom();xi(this._proxy,this.project(t,i),this.getZoomScale(i,1))},this),this._on("unload",this._destroyAnimProxy,this)},_destroyAnimProxy:function(){ui(this._proxy),delete this._proxy},_catchTransitionEnd:function(t){this._animatingZoom&&0<=t.propertyName.indexOf("transform")&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,i,e){if(this._animatingZoom)return!0;if(e=e||{},!this._zoomAnimated||!1===e.animate||this._nothingToAnimate()||Math.abs(i-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(o))&&(M(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,e,n){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,pi(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:n}),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&mi(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),M(function(){this._moveEnd(!0)},this))}});function $i(t){return new Qi(t)}var Qi=Z.extend({options:{position:"topright"},initialize:function(t){p(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),n=t._controlCorners[e];return pi(i,"leaflet-control"),-1!==e.indexOf("bottom")?n.insertBefore(i,n.firstChild):n.appendChild(i),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(ui(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0<t.screenX&&0<t.screenY&&this._map.getContainer().focus()}});Ji.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){var n=this._controlCorners={},o="leaflet-",s=this._controlContainer=hi("div",o+"control-container",this._container);function t(t,i){var e=o+t+" "+o+i;n[t+i]=hi("div",e,s)}t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){for(var t in this._controlCorners)ui(this._controlCorners[t]);ui(this._controlContainer),delete this._controlCorners,delete this._controlContainer}});var te=Qi.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,i,e,n){return e<n?-1:n<e?1:0}},initialize:function(t,i,e){for(var n in p(this,e),this._layerControlInputs=[],this._layers=[],this._lastZIndex=0,this._handlingClick=!1,t)this._addLayer(t[n],n);for(n in i)this._addLayer(i[n],n,!0)},onAdd:function(t){this._initLayout(),this._update(),(this._map=t).on("zoomend",this._checkDisabledLayers,this);for(var i=0;i<this._layers.length;i++)this._layers[i].layer.on("add remove",this._onLayerChange,this);return this._container},addTo:function(t){return Qi.prototype.addTo.call(this,t),this._expandIfNotCollapsed()},onRemove:function(){this._map.off("zoomend",this._checkDisabledLayers,this);for(var t=0;t<this._layers.length;t++)this._layers[t].layer.off("add remove",this._onLayerChange,this)},addBaseLayer:function(t,i){return this._addLayer(t,i),this._map?this._update():this},addOverlay:function(t,i){return this._addLayer(t,i,!0),this._map?this._update():this},removeLayer:function(t){t.off("add remove",this._onLayerChange,this);var i=this._getLayer(u(t));return i&&this._layers.splice(this._layers.indexOf(i),1),this._map?this._update():this},expand:function(){pi(this._container,"leaflet-control-layers-expanded"),this._section.style.height=null;var t=this._map.getSize().y-(this._container.offsetTop+50);return t<this._section.clientHeight?(pi(this._section,"leaflet-control-layers-scrollbar"),this._section.style.height=t+"px"):mi(this._section,"leaflet-control-layers-scrollbar"),this._checkDisabledLayers(),this},collapse:function(){return mi(this._container,"leaflet-control-layers-expanded"),this},_initLayout:function(){var t="leaflet-control-layers",i=this._container=hi("div",t),e=this.options.collapsed;i.setAttribute("aria-haspopup",!0),Ni(i),Ri(i);var n=this._section=hi("section",t+"-list");e&&(this._map.on("click",this.collapse,this),st||Ei(i,{mouseenter:this.expand,mouseleave:this.collapse},this));var o=this._layersLink=hi("a",t+"-toggle",i);o.href="#",o.title="Layers",Tt?(Ei(o,"click",ji),Ei(o,"click",this.expand,this)):Ei(o,"focus",this.expand,this),e||this.expand(),this._baseLayersList=hi("div",t+"-base",n),this._separator=hi("div",t+"-separator",n),this._overlaysList=hi("div",t+"-overlays",n),i.appendChild(n)},_getLayer:function(t){for(var i=0;i<this._layers.length;i++)if(this._layers[i]&&u(this._layers[i].layer)===t)return this._layers[i]},_addLayer:function(t,i,e){this._map&&t.on("add remove",this._onLayerChange,this),this._layers.push({layer:t,name:i,overlay:e}),this.options.sortLayers&&this._layers.sort(a(function(t,i){return this.options.sortFunction(t.layer,i.layer,t.name,i.name)},this)),this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex)),this._expandIfNotCollapsed()},_update:function(){if(!this._container)return this;li(this._baseLayersList),li(this._overlaysList),this._layerControlInputs=[];var t,i,e,n,o=0;for(e=0;e<this._layers.length;e++)n=this._layers[e],this._addItem(n),i=i||n.overlay,t=t||!n.overlay,o+=n.overlay?0:1;return this.options.hideSingleBase&&(t=t&&1<o,this._baseLayersList.style.display=t?"":"none"),this._separator.style.display=i&&t?"":"none",this},_onLayerChange:function(t){this._handlingClick||this._update();var i=this._getLayer(u(t.target)),e=i.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;e&&this._map.fire(e,i)},_createRadioElement:function(t,i){var e='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"'+(i?' checked="checked"':"")+"/>",n=document.createElement("div");return n.innerHTML=e,n.firstChild},_addItem:function(t){var i,e=document.createElement("label"),n=this._map.hasLayer(t.layer);t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=n):i=this._createRadioElement("leaflet-base-layers_"+u(this),n),this._layerControlInputs.push(i),i.layerId=u(t.layer),Ei(i,"click",this._onInputClick,this);var o=document.createElement("span");o.innerHTML=" "+t.name;var s=document.createElement("div");return e.appendChild(s),s.appendChild(i),s.appendChild(o),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;0<=s;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;s<o.length;s++)this._map.hasLayer(o[s])&&this._map.removeLayer(o[s]);for(s=0;s<n.length;s++)this._map.hasLayer(n[s])||this._map.addLayer(n[s]);this._handlingClick=!1,this._refocusOnMap()},_checkDisabledLayers:function(){for(var t,i,e=this._layerControlInputs,n=this._map.getZoom(),o=e.length-1;0<=o;o--)t=e[o],i=this._getLayer(t.layerId).layer,t.disabled=void 0!==i.options.minZoom&&n<i.options.minZoom||void 0!==i.options.maxZoom&&n>i.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),ie=Qi.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"&#x2212;",zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=hi("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoom<this._map.getMaxZoom()&&this._map.zoomIn(this._map.options.zoomDelta*(t.shiftKey?3:1))},_zoomOut:function(t){!this._disabled&&this._map._zoom>this._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){var s=hi("a",e,n);return s.innerHTML=t,s.href="#",s.title=i,s.setAttribute("role","button"),s.setAttribute("aria-label",i),Ni(s),Ei(s,"click",ji),Ei(s,"click",o,this),Ei(s,"click",this._refocusOnMap,this),s},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";mi(this._zoomInButton,i),mi(this._zoomOutButton,i),!this._disabled&&t._zoom!==t.getMinZoom()||pi(this._zoomOutButton,i),!this._disabled&&t._zoom!==t.getMaxZoom()||pi(this._zoomInButton,i)}});Ji.mergeOptions({zoomControl:!0}),Ji.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new ie,this.addControl(this.zoomControl))});var ee=Qi.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i="leaflet-control-scale",e=hi("div",i),n=this.options;return this._addScales(n,i+"-line",e),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),e},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=hi("div",i,e)),t.imperial&&(this._iScale=hi("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,e=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t),e=i<1e3?i+" m":i/1e3+" km";this._updateScale(this._mScale,e,i/t)},_updateImperial:function(t){var i,e,n,o=3.2808399*t;5280<o?(i=o/5280,e=this._getRoundNum(i),this._updateScale(this._iScale,e+" mi",e/i)):(n=this._getRoundNum(o),this._updateScale(this._iScale,n+" ft",n/o))},_updateScale:function(t,i,e){t.style.width=Math.round(this.options.maxWidth*e)+"px",t.innerHTML=i},_getRoundNum:function(t){var i=Math.pow(10,(Math.floor(t)+"").length-1),e=t/i;return i*(e=10<=e?10:5<=e?5:3<=e?3:2<=e?2:1)}}),ne=Qi.extend({options:{position:"bottomright",prefix:'<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'},initialize:function(t){p(this,t),this._attributions={}},onAdd:function(t){for(var i in(t.attributionControl=this)._container=hi("div","leaflet-control-attribution"),Ni(this._container),t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(", ")),this._container.innerHTML=e.join(" | ")}}});Ji.mergeOptions({attributionControl:!0}),Ji.addInitHook(function(){this.options.attributionControl&&(new ne).addTo(this)});Qi.Layers=te,Qi.Zoom=ie,Qi.Scale=ee,Qi.Attribution=ne,$i.layers=function(t,i,e){return new te(t,i,e)},$i.zoom=function(t){return new ie(t)},$i.scale=function(t){return new ee(t)},$i.attribution=function(t){return new ne(t)};var oe=Z.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}});oe.addTo=function(t,i){return t.addHandler(i,this),this};var se,re={Events:E},ae=Tt?"touchstart mousedown":"mousedown",he={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},ue={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},le=k.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){p(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(Ei(this._dragStartTarget,ae,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(le._dragging===this&&this.finishDrag(),Bi(this._dragStartTarget,ae,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!di(this._element,"leaflet-zoom-anim")&&!(le._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((le._dragging=this)._preventOutline&&zi(this._element),Li(),$t(),this._moving)))){this.fire("down");var i=t.touches?t.touches[0]:t,e=Ci(this._element);this._startPoint=new B(i.clientX,i.clientY),this._parentScale=Si(e),Ei(document,ue[t.type],this._onMove,this),Ei(document,he[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&1<t.touches.length)this._moved=!0;else{var i=t.touches&&1===t.touches.length?t.touches[0]:t,e=new B(i.clientX,i.clientY)._subtract(this._startPoint);(e.x||e.y)&&(Math.abs(e.x)+Math.abs(e.y)<this.options.clickTolerance||(e.x/=this._parentScale.x,e.y/=this._parentScale.y,Di(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=Pi(this._element).subtract(e),pi(document.body,"leaflet-dragging"),this._lastTarget=t.target||t.srcElement,window.SVGElementInstance&&this._lastTarget instanceof SVGElementInstance&&(this._lastTarget=this._lastTarget.correspondingUseElement),pi(this._lastTarget,"leaflet-drag-target")),this._newPos=this._startPos.add(e),this._moving=!0,C(this._animRequest),this._lastEvent=t,this._animRequest=M(this._updatePosition,this,!0)))}},_updatePosition:function(){var t={originalEvent:this._lastEvent};this.fire("predrag",t),wi(this._element,this._newPos),this.fire("drag",t)},_onUp:function(t){!t._simulated&&this._enabled&&this.finishDrag()},finishDrag:function(){for(var t in mi(document.body,"leaflet-dragging"),this._lastTarget&&(mi(this._lastTarget,"leaflet-drag-target"),this._lastTarget=null),ue)Bi(document,ue[t],this._onMove,this),Bi(document,he[t],this._onUp,this);Ti(),Qt(),this._moved&&this._moving&&(C(this._animRequest),this.fire("dragend",{distance:this._newPos.distanceTo(this._startPos)})),this._moving=!1,le._dragging=!1}});function ce(t,i){if(!i||!t.length)return t.slice();var e=i*i;return t=function(t,i){var e=t.length,n=new(typeof Uint8Array!=void 0+""?Uint8Array:Array)(e);n[0]=n[e-1]=1,function t(i,e,n,o,s){var r,a,h,u=0;for(a=o+1;a<=s-1;a++)h=fe(i[a],i[o],i[s],!0),u<h&&(r=a,u=h);n<u&&(e[r]=1,t(i,e,n,o,r),t(i,e,n,r,s))}(t,n,i,0,e-1);var o,s=[];for(o=0;o<e;o++)n[o]&&s.push(t[o]);return s}(t=function(t,i){for(var e=[t[0]],n=1,o=0,s=t.length;n<s;n++)r=t[n],a=t[o],void 0,h=a.x-r.x,u=a.y-r.y,i<h*h+u*u&&(e.push(t[n]),o=n);var r,a,h,u;o<s-1&&e.push(t[s-1]);return e}(t,e),e)}function _e(t,i,e){return Math.sqrt(fe(t,i,e,!0))}function de(t,i,e,n,o){var s,r,a,h=n?se:me(t,e),u=me(i,e);for(se=u;;){if(!(h|u))return[t,i];if(h&u)return!1;a=me(r=pe(t,i,s=h||u,e,o),e),s===h?(t=r,h=a):(i=r,u=a)}}function pe(t,i,e,n,o){var s,r,a=i.x-t.x,h=i.y-t.y,u=n.min,l=n.max;return 8&e?(s=t.x+a*(l.y-t.y)/h,r=l.y):4&e?(s=t.x+a*(u.y-t.y)/h,r=u.y):2&e?(s=l.x,r=t.y+h*(l.x-t.x)/a):1&e&&(s=u.x,r=t.y+h*(u.x-t.x)/a),new B(s,r,o)}function me(t,i){var e=0;return t.x<i.min.x?e|=1:t.x>i.max.x&&(e|=2),t.y<i.min.y?e|=4:t.y>i.max.y&&(e|=8),e}function fe(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return 0<u&&(1<(o=((t.x-s)*a+(t.y-r)*h)/u)?(s=e.x,r=e.y):0<o&&(s+=a*o,r+=h*o)),a=t.x-s,h=t.y-r,n?a*a+h*h:new B(s,r)}function ge(t){return!v(t[0])||"object"!=typeof t[0][0]&&void 0!==t[0][0]}function ve(t){return console.warn("Deprecated use of _flat, please use L.LineUtil.isFlat instead."),ge(t)}var ye=(Object.freeze||Object)({simplify:ce,pointToSegmentDistance:_e,closestPointOnSegment:function(t,i,e){return fe(t,i,e)},clipSegment:de,_getEdgeIntersection:pe,_getBitCode:me,_sqClosestPointOnSegment:fe,isFlat:ge,_flat:ve});function xe(t,i,e){var n,o,s,r,a,h,u,l,c,_=[1,4,2,8];for(o=0,u=t.length;o<u;o++)t[o]._code=me(t[o],i);for(r=0;r<4;r++){for(l=_[r],n=[],o=0,s=(u=t.length)-1;o<u;s=o++)a=t[o],h=t[s],a._code&l?h._code&l||((c=pe(h,a,l,i,e))._code=me(c,i),n.push(c)):(h._code&l&&((c=pe(h,a,l,i,e))._code=me(c,i),n.push(c)),n.push(a));t=n}return t}var we,Pe=(Object.freeze||Object)({clipPolygon:xe}),be={project:function(t){return new B(t.lng,t.lat)},unproject:function(t){return new j(t.y,t.x)},bounds:new O([-180,-90],[180,90])},Le={R:6378137,R_MINOR:6356752.314245179,bounds:new O([-20037508.34279,-15496570.73972],[20037508.34279,18764656.23138]),project:function(t){var i=Math.PI/180,e=this.R,n=t.lat*i,o=this.R_MINOR/e,s=Math.sqrt(1-o*o),r=s*Math.sin(n),a=Math.tan(Math.PI/4-n/2)/Math.pow((1-r)/(1+r),s/2);return n=-e*Math.log(Math.max(a,1e-10)),new B(t.lng*i*e,n)},unproject:function(t){for(var i,e=180/Math.PI,n=this.R,o=this.R_MINOR/n,s=Math.sqrt(1-o*o),r=Math.exp(-t.y/n),a=Math.PI/2-2*Math.atan(r),h=0,u=.1;h<15&&1e-7<Math.abs(u);h++)i=s*Math.sin(a),i=Math.pow((1-i)/(1+i),s/2),a+=u=Math.PI/2-2*Math.atan(r*i)-a;return new j(a*e,t.x*e/n)}},Te=(Object.freeze||Object)({LonLat:be,Mercator:Le,SphericalMercator:q}),ze=h({},U,{code:"EPSG:3395",projection:Le,transformation:(we=.5/(Math.PI*Le.R),K(we,.5,-we,.5))}),Me=h({},U,{code:"EPSG:4326",projection:be,transformation:K(1/180,1,-1/180,.5)}),Ce=h({},F,{projection:be,transformation:K(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,i){var e=i.lng-t.lng,n=i.lat-t.lat;return Math.sqrt(e*e+n*n)},infinite:!0});F.Earth=U,F.EPSG3395=ze,F.EPSG3857=X,F.EPSG900913=J,F.EPSG4326=Me,F.Simple=Ce;var Se=k.extend({options:{pane:"overlayPane",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[u(t)]=this},removeInteractiveTarget:function(t){return delete this._map._targets[u(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var i=t.target;if(i.hasLayer(this)){if(this._map=i,this._zoomAnimated=i._zoomAnimated,this.getEvents){var e=this.getEvents();i.on(e,this),this.once("remove",function(){i.off(e,this)},this)}this.onAdd(i),this.getAttribution&&i.attributionControl&&i.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),i.fire("layeradd",{layer:this})}}});Ji.include({addLayer:function(t){if(!t._layerAdd)throw new Error("The provided object is not a Layer.");var i=u(t);return this._layers[i]||((this._layers[i]=t)._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t)),this},removeLayer:function(t){var i=u(t);return this._layers[i]&&(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[i],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null),this},hasLayer:function(t){return!!t&&u(t)in this._layers},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},_addLayers:function(t){for(var i=0,e=(t=t?v(t)?t:[t]:[]).length;i<e;i++)this.addLayer(t[i])},_addZoomLimit:function(t){!isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[u(t)]=t,this._updateZoomLevels())},_removeZoomLimit:function(t){var i=u(t);this._zoomBoundLayers[i]&&(delete this._zoomBoundLayers[i],this._updateZoomLevels())},_updateZoomLevels:function(){var t=1/0,i=-1/0,e=this._getZoomSpan();for(var n in this._zoomBoundLayers){var o=this._zoomBoundLayers[n].options;t=void 0===o.minZoom?t:Math.min(t,o.minZoom),i=void 0===o.maxZoom?i:Math.max(i,o.maxZoom)}this._layersMaxZoom=i===-1/0?void 0:i,this._layersMinZoom=t===1/0?void 0:t,e!==this._getZoomSpan()&&this.fire("zoomlevelschange"),void 0===this.options.maxZoom&&this._layersMaxZoom&&this.getZoom()>this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()<this._layersMinZoom&&this.setZoom(this._layersMinZoom)}});var Ze=Se.extend({initialize:function(t,i){var e,n;if(p(this,i),this._layers={},t)for(e=0,n=t.length;e<n;e++)this.addLayer(t[e])},addLayer:function(t){var i=this.getLayerId(t);return this._layers[i]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var i=t in this._layers?t:this.getLayerId(t);return this._map&&this._layers[i]&&this._map.removeLayer(this._layers[i]),delete this._layers[i],this},hasLayer:function(t){return!!t&&(t in this._layers||this.getLayerId(t)in this._layers)},clearLayers:function(){return this.eachLayer(this.removeLayer,this)},invoke:function(t){var i,e,n=Array.prototype.slice.call(arguments,1);for(i in this._layers)(e=this._layers[i])[t]&&e[t].apply(e,n);return this},onAdd:function(t){this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t)},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},getLayer:function(t){return this._layers[t]},getLayers:function(){var t=[];return this.eachLayer(t.push,t),t},setZIndex:function(t){return this.invoke("setZIndex",t)},getLayerId:function(t){return u(t)}}),Ee=Ze.extend({addLayer:function(t){return this.hasLayer(t)?this:(t.addEventParent(this),Ze.prototype.addLayer.call(this,t),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return this.hasLayer(t)?(t in this._layers&&(t=this._layers[t]),t.removeEventParent(this),Ze.prototype.removeLayer.call(this,t),this.fire("layerremove",{layer:t})):this},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new N;for(var i in this._layers){var e=this._layers[i];t.extend(e.getBounds?e.getBounds():e.getLatLng())}return t}}),ke=Z.extend({options:{popupAnchor:[0,0],tooltipAnchor:[0,0]},initialize:function(t){p(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,i){var e=this._getIconUrl(t);if(!e){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n=this._createImg(e,i&&"IMG"===i.tagName?i:null);return this._setIconStyles(n,t),n},_setIconStyles:function(t,i){var e=this.options,n=e[i+"Size"];"number"==typeof n&&(n=[n,n]);var o=I(n),s=I("shadow"===i&&e.shadowAnchor||e.iconAnchor||o&&o.divideBy(2,!0));t.className="leaflet-marker-"+i+" "+(e.className||""),s&&(t.style.marginLeft=-s.x+"px",t.style.marginTop=-s.y+"px"),o&&(t.style.width=o.x+"px",t.style.height=o.y+"px")},_createImg:function(t,i){return(i=i||document.createElement("img")).src=t,i},_getIconUrl:function(t){return Ct&&this.options[t+"RetinaUrl"]||this.options[t+"Url"]}});var Be=ke.extend({options:{iconUrl:"marker-icon.png",iconRetinaUrl:"marker-icon-2x.png",shadowUrl:"marker-shadow.png",iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],tooltipAnchor:[16,-28],shadowSize:[41,41]},_getIconUrl:function(t){return Be.imagePath||(Be.imagePath=this._detectIconPath()),(this.options.imagePath||Be.imagePath)+ke.prototype._getIconUrl.call(this,t)},_detectIconPath:function(){var t=hi("div","leaflet-default-icon-path",document.body),i=ai(t,"background-image")||ai(t,"backgroundImage");return document.body.removeChild(t),i=null===i||0!==i.indexOf("url")?"":i.replace(/^url\(["']?/,"").replace(/marker-icon\.png["']?\)$/,"")}}),Ae=oe.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new le(t,t,!0)),this._draggable.on({dragstart:this._onDragStart,predrag:this._onPreDrag,drag:this._onDrag,dragend:this._onDragEnd},this).enable(),pi(t,"leaflet-marker-draggable")},removeHooks:function(){this._draggable.off({dragstart:this._onDragStart,predrag:this._onPreDrag,drag:this._onDrag,dragend:this._onDragEnd},this).disable(),this._marker._icon&&mi(this._marker._icon,"leaflet-marker-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_adjustPan:function(t){var i=this._marker,e=i._map,n=this._marker.options.autoPanSpeed,o=this._marker.options.autoPanPadding,s=Pi(i._icon),r=e.getPixelBounds(),a=e.getPixelOrigin(),h=R(r.min._subtract(a).add(o),r.max._subtract(a).subtract(o));if(!h.contains(s)){var u=I((Math.max(h.max.x,s.x)-h.max.x)/(r.max.x-h.max.x)-(Math.min(h.min.x,s.x)-h.min.x)/(r.min.x-h.min.x),(Math.max(h.max.y,s.y)-h.max.y)/(r.max.y-h.max.y)-(Math.min(h.min.y,s.y)-h.min.y)/(r.min.y-h.min.y)).multiplyBy(n);e.panBy(u,{animate:!1}),this._draggable._newPos._add(u),this._draggable._startPos._add(u),wi(i._icon,this._draggable._newPos),this._onDrag(t),this._panRequest=M(this._adjustPan.bind(this,t))}},_onDragStart:function(){this._oldLatLng=this._marker.getLatLng(),this._marker.closePopup().fire("movestart").fire("dragstart")},_onPreDrag:function(t){this._marker.options.autoPan&&(C(this._panRequest),this._panRequest=M(this._adjustPan.bind(this,t)))},_onDrag:function(t){var i=this._marker,e=i._shadow,n=Pi(i._icon),o=i._map.layerPointToLatLng(n);e&&wi(e,n),i._latlng=o,t.latlng=o,t.oldLatLng=this._oldLatLng,i.fire("move",t).fire("drag",t)},_onDragEnd:function(t){C(this._panRequest),delete this._oldLatLng,this._marker.fire("moveend").fire("dragend",t)}}),Ie=Se.extend({options:{icon:new Be,interactive:!0,keyboard:!0,title:"",alt:"",zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250,pane:"markerPane",shadowPane:"shadowPane",bubblingMouseEvents:!1,draggable:!1,autoPan:!1,autoPanPadding:[50,50],autoPanSpeed:10},initialize:function(t,i){p(this,i),this._latlng=W(t)},onAdd:function(t){this._zoomAnimated=this._zoomAnimated&&t.options.markerZoomAnimation,this._zoomAnimated&&t.on("zoomanim",this._animateZoom,this),this._initIcon(),this.update()},onRemove:function(t){this.dragging&&this.dragging.enabled()&&(this.options.draggable=!0,this.dragging.removeHooks()),delete this.dragging,this._zoomAnimated&&t.off("zoomanim",this._animateZoom,this),this._removeIcon(),this._removeShadow()},getEvents:function(){return{zoom:this.update,viewreset:this.update}},getLatLng:function(){return this._latlng},setLatLng:function(t){var i=this._latlng;return this._latlng=W(t),this.update(),this.fire("move",{oldLatLng:i,latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update()},getIcon:function(){return this.options.icon},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup,this._popup.options),this},getElement:function(){return this._icon},update:function(){if(this._icon&&this._map){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,i="leaflet-zoom-"+(this._zoomAnimated?"animated":"hide"),e=t.icon.createIcon(this._icon),n=!1;e!==this._icon&&(this._icon&&this._removeIcon(),n=!0,t.title&&(e.title=t.title),"IMG"===e.tagName&&(e.alt=t.alt||"")),pi(e,i),t.keyboard&&(e.tabIndex="0"),this._icon=e,t.riseOnHover&&this.on({mouseover:this._bringToFront,mouseout:this._resetZIndex});var o=t.icon.createShadow(this._shadow),s=!1;o!==this._shadow&&(this._removeShadow(),s=!0),o&&(pi(o,i),o.alt=""),this._shadow=o,t.opacity<1&&this._updateOpacity(),n&&this.getPane().appendChild(this._icon),this._initInteraction(),o&&s&&this.getPane(t.shadowPane).appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&this.off({mouseover:this._bringToFront,mouseout:this._resetZIndex}),ui(this._icon),this.removeInteractiveTarget(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&ui(this._shadow),this._shadow=null},_setPos:function(t){wi(this._icon,t),this._shadow&&wi(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(i)},_initInteraction:function(){if(this.options.interactive&&(pi(this._icon,"leaflet-interactive"),this.addInteractiveTarget(this._icon),Ae)){var t=this.options.draggable;this.dragging&&(t=this.dragging.enabled(),this.dragging.disable()),this.dragging=new Ae(this),t&&this.dragging.enable()}},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},_updateOpacity:function(){var t=this.options.opacity;this._icon&&vi(this._icon,t),this._shadow&&vi(this._shadow,t)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)},_getPopupAnchor:function(){return this.options.icon.options.popupAnchor},_getTooltipAnchor:function(){return this.options.icon.options.tooltipAnchor}});var Oe=Se.extend({options:{stroke:!0,color:"#3388ff",weight:3,opacity:1,lineCap:"round",lineJoin:"round",dashArray:null,dashOffset:null,fill:!1,fillColor:null,fillOpacity:.2,fillRule:"evenodd",interactive:!0,bubblingMouseEvents:!0},beforeAdd:function(t){this._renderer=t.getRenderer(this)},onAdd:function(){this._renderer._initPath(this),this._reset(),this._renderer._addPath(this)},onRemove:function(){this._renderer._removePath(this)},redraw:function(){return this._map&&this._renderer._updatePath(this),this},setStyle:function(t){return p(this,t),this._renderer&&(this._renderer._updateStyle(this),this.options.stroke&&t.hasOwnProperty("weight")&&this._updateBounds()),this},bringToFront:function(){return this._renderer&&this._renderer._bringToFront(this),this},bringToBack:function(){return this._renderer&&this._renderer._bringToBack(this),this},getElement:function(){return this._path},_reset:function(){this._project(),this._update()},_clickTolerance:function(){return(this.options.stroke?this.options.weight/2:0)+this._renderer.options.tolerance}}),Re=Oe.extend({options:{fill:!0,radius:10},initialize:function(t,i){p(this,i),this._latlng=W(t),this._radius=this.options.radius},setLatLng:function(t){return this._latlng=W(t),this.redraw(),this.fire("move",{latlng:this._latlng})},getLatLng:function(){return this._latlng},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()},getRadius:function(){return this._radius},setStyle:function(t){var i=t&&t.radius||this._radius;return Oe.prototype.setStyle.call(this,t),this.setRadius(i),this},_project:function(){this._point=this._map.latLngToLayerPoint(this._latlng),this._updateBounds()},_updateBounds:function(){var t=this._radius,i=this._radiusY||t,e=this._clickTolerance(),n=[t+e,i+e];this._pxBounds=new O(this._point.subtract(n),this._point.add(n))},_update:function(){this._map&&this._updatePath()},_updatePath:function(){this._renderer._updateCircle(this)},_empty:function(){return this._radius&&!this._renderer._bounds.intersects(this._pxBounds)},_containsPoint:function(t){return t.distanceTo(this._point)<=this._radius+this._clickTolerance()}});var Ne=Re.extend({initialize:function(t,i,e){if("number"==typeof i&&(i=h({},e,{radius:i})),p(this,i),this._latlng=W(t),isNaN(this.options.radius))throw new Error("Circle radius cannot be NaN");this._mRadius=this.options.radius},setRadius:function(t){return this._mRadius=t,this.redraw()},getRadius:function(){return this._mRadius},getBounds:function(){var t=[this._radius,this._radiusY||this._radius];return new N(this._map.layerPointToLatLng(this._point.subtract(t)),this._map.layerPointToLatLng(this._point.add(t)))},setStyle:Oe.prototype.setStyle,_project:function(){var t=this._latlng.lng,i=this._latlng.lat,e=this._map,n=e.options.crs;if(n.distance===U.distance){var o=Math.PI/180,s=this._mRadius/U.R/o,r=e.project([i+s,t]),a=e.project([i-s,t]),h=r.add(a).divideBy(2),u=e.unproject(h).lat,l=Math.acos((Math.cos(s*o)-Math.sin(i*o)*Math.sin(u*o))/(Math.cos(i*o)*Math.cos(u*o)))/o;!isNaN(l)&&0!==l||(l=s/Math.cos(Math.PI/180*i)),this._point=h.subtract(e.getPixelOrigin()),this._radius=isNaN(l)?0:h.x-e.project([u,t-l]).x,this._radiusY=h.y-r.y}else{var c=n.unproject(n.project(this._latlng).subtract([this._mRadius,0]));this._point=e.latLngToLayerPoint(this._latlng),this._radius=this._point.x-e.latLngToLayerPoint(c).x}this._updateBounds()}});var De=Oe.extend({options:{smoothFactor:1,noClip:!1},initialize:function(t,i){p(this,i),this._setLatLngs(t)},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._setLatLngs(t),this.redraw()},isEmpty:function(){return!this._latlngs.length},closestLayerPoint:function(t){for(var i,e,n=1/0,o=null,s=fe,r=0,a=this._parts.length;r<a;r++)for(var h=this._parts[r],u=1,l=h.length;u<l;u++){var c=s(t,i=h[u-1],e=h[u],!0);c<n&&(n=c,o=s(t,i,e))}return o&&(o.distance=Math.sqrt(n)),o},getCenter:function(){if(!this._map)throw new Error("Must add layer to map before using getCenter()");var t,i,e,n,o,s,r,a=this._rings[0],h=a.length;if(!h)return null;for(i=t=0;t<h-1;t++)i+=a[t].distanceTo(a[t+1])/2;if(0===i)return this._map.layerPointToLatLng(a[0]);for(n=t=0;t<h-1;t++)if(o=a[t],s=a[t+1],i<(n+=e=o.distanceTo(s)))return r=(n-i)/e,this._map.layerPointToLatLng([s.x-r*(s.x-o.x),s.y-r*(s.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,i){return i=i||this._defaultShape(),t=W(t),i.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new N,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return ge(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var i=[],e=ge(t),n=0,o=t.length;n<o;n++)e?(i[n]=W(t[n]),this._bounds.extend(i[n])):i[n]=this._convertLatLngs(t[n]);return i},_project:function(){var t=new O;this._rings=[],this._projectLatlngs(this._latlngs,this._rings,t),this._bounds.isValid()&&t.isValid()&&(this._rawPxBounds=t,this._updateBounds())},_updateBounds:function(){var t=this._clickTolerance(),i=new B(t,t);this._pxBounds=new O([this._rawPxBounds.min.subtract(i),this._rawPxBounds.max.add(i)])},_projectLatlngs:function(t,i,e){var n,o,s=t[0]instanceof j,r=t.length;if(s){for(o=[],n=0;n<r;n++)o[n]=this._map.latLngToLayerPoint(t[n]),e.extend(o[n]);i.push(o)}else for(n=0;n<r;n++)this._projectLatlngs(t[n],i,e)},_clipPoints:function(){var t=this._renderer._bounds;if(this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else{var i,e,n,o,s,r,a,h=this._parts;for(n=i=0,o=this._rings.length;i<o;i++)for(e=0,s=(a=this._rings[i]).length;e<s-1;e++)(r=de(a[e],a[e+1],t,e,!0))&&(h[n]=h[n]||[],h[n].push(r[0]),r[1]===a[e+1]&&e!==s-2||(h[n].push(r[1]),n++))}},_simplifyPoints:function(){for(var t=this._parts,i=this.options.smoothFactor,e=0,n=t.length;e<n;e++)t[e]=ce(t[e],i)},_update:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),this._updatePath())},_updatePath:function(){this._renderer._updatePoly(this)},_containsPoint:function(t,i){var e,n,o,s,r,a,h=this._clickTolerance();if(!this._pxBounds||!this._pxBounds.contains(t))return!1;for(e=0,s=this._parts.length;e<s;e++)for(n=0,o=(r=(a=this._parts[e]).length)-1;n<r;o=n++)if((i||0!==n)&&_e(t,a[o],a[n])<=h)return!0;return!1}});De._flat=ve;var je=De.extend({options:{fill:!0},isEmpty:function(){return!this._latlngs.length||!this._latlngs[0].length},getCenter:function(){if(!this._map)throw new Error("Must add layer to map before using getCenter()");var t,i,e,n,o,s,r,a,h,u=this._rings[0],l=u.length;if(!l)return null;for(s=r=a=0,t=0,i=l-1;t<l;i=t++)e=u[t],n=u[i],o=e.y*n.x-n.y*e.x,r+=(e.x+n.x)*o,a+=(e.y+n.y)*o,s+=3*o;return h=0===s?u[0]:[r/s,a/s],this._map.layerPointToLatLng(h)},_convertLatLngs:function(t){var i=De.prototype._convertLatLngs.call(this,t),e=i.length;return 2<=e&&i[0]instanceof j&&i[0].equals(i[e-1])&&i.pop(),i},_setLatLngs:function(t){De.prototype._setLatLngs.call(this,t),ge(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return ge(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,i=this.options.weight,e=new B(i,i);if(t=new O(t.min.subtract(e),t.max.add(e)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var n,o=0,s=this._rings.length;o<s;o++)(n=xe(this._rings[o],t,!0)).length&&this._parts.push(n)},_updatePath:function(){this._renderer._updatePoly(this,!0)},_containsPoint:function(t){var i,e,n,o,s,r,a,h,u=!1;if(!this._pxBounds||!this._pxBounds.contains(t))return!1;for(o=0,a=this._parts.length;o<a;o++)for(s=0,r=(h=(i=this._parts[o]).length)-1;s<h;r=s++)e=i[s],n=i[r],e.y>t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||De.prototype._containsPoint.call(this,t,!0)}});var We=Ee.extend({initialize:function(t,i){p(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=v(t)?t:t.features;if(o){for(i=0,e=o.length;i<e;i++)((n=o[i]).geometries||n.geometry||n.features||n.coordinates)&&this.addData(n);return this}var s=this.options;if(s.filter&&!s.filter(t))return this;var r=He(t,s);return r?(r.feature=Ke(t),r.defaultOptions=r.options,this.resetStyle(r),s.onEachFeature&&s.onEachFeature(t,r),this.addLayer(r)):this},resetStyle:function(t){return t.options=h({},t.defaultOptions),this._setLayerStyle(t,this.options.style),this},setStyle:function(i){return this.eachLayer(function(t){this._setLayerStyle(t,i)},this)},_setLayerStyle:function(t,i){t.setStyle&&("function"==typeof i&&(i=i(t.feature)),t.setStyle(i))}});function He(t,i){var e,n,o,s,r="Feature"===t.type?t.geometry:t,a=r?r.coordinates:null,h=[],u=i&&i.pointToLayer,l=i&&i.coordsToLatLng||Fe;if(!a&&!r)return null;switch(r.type){case"Point":return e=l(a),u?u(t,e):new Ie(e);case"MultiPoint":for(o=0,s=a.length;o<s;o++)e=l(a[o]),h.push(u?u(t,e):new Ie(e));return new Ee(h);case"LineString":case"MultiLineString":return n=Ue(a,"LineString"===r.type?0:1,l),new De(n,i);case"Polygon":case"MultiPolygon":return n=Ue(a,"Polygon"===r.type?1:2,l),new je(n,i);case"GeometryCollection":for(o=0,s=r.geometries.length;o<s;o++){var c=He({geometry:r.geometries[o],type:"Feature",properties:t.properties},i);c&&h.push(c)}return new Ee(h);default:throw new Error("Invalid GeoJSON object.")}}function Fe(t){return new j(t[1],t[0],t[2])}function Ue(t,i,e){for(var n,o=[],s=0,r=t.length;s<r;s++)n=i?Ue(t[s],i-1,e):(e||Fe)(t[s]),o.push(n);return o}function Ve(t,i){return i="number"==typeof i?i:6,void 0!==t.alt?[c(t.lng,i),c(t.lat,i),c(t.alt,i)]:[c(t.lng,i),c(t.lat,i)]}function qe(t,i,e,n){for(var o=[],s=0,r=t.length;s<r;s++)o.push(i?qe(t[s],i-1,e,n):Ve(t[s],n));return!i&&e&&o.push(o[0]),o}function Ge(t,i){return t.feature?h({},t.feature,{geometry:i}):Ke(i)}function Ke(t){return"Feature"===t.type||"FeatureCollection"===t.type?t:{type:"Feature",properties:{},geometry:t}}var Ye={toGeoJSON:function(t){return Ge(this,{type:"Point",coordinates:Ve(this.getLatLng(),t)})}};function Xe(t,i){return new We(t,i)}Ie.include(Ye),Ne.include(Ye),Re.include(Ye),De.include({toGeoJSON:function(t){var i=!ge(this._latlngs);return Ge(this,{type:(i?"Multi":"")+"LineString",coordinates:qe(this._latlngs,i?1:0,!1,t)})}}),je.include({toGeoJSON:function(t){var i=!ge(this._latlngs),e=i&&!ge(this._latlngs[0]),n=qe(this._latlngs,e?2:i?1:0,!0,t);return i||(n=[n]),Ge(this,{type:(e?"Multi":"")+"Polygon",coordinates:n})}}),Ze.include({toMultiPoint:function(i){var e=[];return this.eachLayer(function(t){e.push(t.toGeoJSON(i).geometry.coordinates)}),Ge(this,{type:"MultiPoint",coordinates:e})},toGeoJSON:function(n){var t=this.feature&&this.feature.geometry&&this.feature.geometry.type;if("MultiPoint"===t)return this.toMultiPoint(n);var o="GeometryCollection"===t,s=[];return this.eachLayer(function(t){if(t.toGeoJSON){var i=t.toGeoJSON(n);if(o)s.push(i.geometry);else{var e=Ke(i);"FeatureCollection"===e.type?s.push.apply(s,e.features):s.push(e)}}}),o?Ge(this,{geometries:s,type:"GeometryCollection"}):{type:"FeatureCollection",features:s}}});var Je=Xe,$e=Se.extend({options:{opacity:1,alt:"",interactive:!1,crossOrigin:!1,errorOverlayUrl:"",zIndex:1,className:""},initialize:function(t,i,e){this._url=t,this._bounds=D(i),p(this,e)},onAdd:function(){this._image||(this._initImage(),this.options.opacity<1&&this._updateOpacity()),this.options.interactive&&(pi(this._image,"leaflet-interactive"),this.addInteractiveTarget(this._image)),this.getPane().appendChild(this._image),this._reset()},onRemove:function(){ui(this._image),this.options.interactive&&this.removeInteractiveTarget(this._image)},setOpacity:function(t){return this.options.opacity=t,this._image&&this._updateOpacity(),this},setStyle:function(t){return t.opacity&&this.setOpacity(t.opacity),this},bringToFront:function(){return this._map&&ci(this._image),this},bringToBack:function(){return this._map&&_i(this._image),this},setUrl:function(t){return this._url=t,this._image&&(this._image.src=t),this},setBounds:function(t){return this._bounds=D(t),this._map&&this._reset(),this},getEvents:function(){var t={zoom:this._reset,viewreset:this._reset};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},getBounds:function(){return this._bounds},getElement:function(){return this._image},_initImage:function(){var t="IMG"===this._url.tagName,i=this._image=t?this._url:hi("img");pi(i,"leaflet-image-layer"),this._zoomAnimated&&pi(i,"leaflet-zoom-animated"),this.options.className&&pi(i,this.options.className),i.onselectstart=l,i.onmousemove=l,i.onload=a(this.fire,this,"load"),i.onerror=a(this._overlayOnError,this,"error"),!this.options.crossOrigin&&""!==this.options.crossOrigin||(i.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),this.options.zIndex&&this._updateZIndex(),t?this._url=i.src:(i.src=this._url,i.alt=this.options.alt)},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),e=this._map._latLngBoundsToNewLayerBounds(this._bounds,t.zoom,t.center).min;xi(this._image,e,i)},_reset:function(){var t=this._image,i=new O(this._map.latLngToLayerPoint(this._bounds.getNorthWest()),this._map.latLngToLayerPoint(this._bounds.getSouthEast())),e=i.getSize();wi(t,i.min),t.style.width=e.x+"px",t.style.height=e.y+"px"},_updateOpacity:function(){vi(this._image,this.options.opacity)},_updateZIndex:function(){this._image&&void 0!==this.options.zIndex&&null!==this.options.zIndex&&(this._image.style.zIndex=this.options.zIndex)},_overlayOnError:function(){this.fire("error");var t=this.options.errorOverlayUrl;t&&this._url!==t&&(this._url=t,this._image.src=t)}}),Qe=$e.extend({options:{autoplay:!0,loop:!0,keepAspectRatio:!0},_initImage:function(){var t="VIDEO"===this._url.tagName,i=this._image=t?this._url:hi("video");if(pi(i,"leaflet-image-layer"),this._zoomAnimated&&pi(i,"leaflet-zoom-animated"),i.onselectstart=l,i.onmousemove=l,i.onloadeddata=a(this.fire,this,"load"),t){for(var e=i.getElementsByTagName("source"),n=[],o=0;o<e.length;o++)n.push(e[o].src);this._url=0<e.length?n:[i.src]}else{v(this._url)||(this._url=[this._url]),!this.options.keepAspectRatio&&i.style.hasOwnProperty("objectFit")&&(i.style.objectFit="fill"),i.autoplay=!!this.options.autoplay,i.loop=!!this.options.loop;for(var s=0;s<this._url.length;s++){var r=hi("source");r.src=this._url[s],i.appendChild(r)}}}});var tn=$e.extend({_initImage:function(){var t=this._image=this._url;pi(t,"leaflet-image-layer"),this._zoomAnimated&&pi(t,"leaflet-zoom-animated"),t.onselectstart=l,t.onmousemove=l}});var en=Se.extend({options:{offset:[0,7],className:"",pane:"popupPane"},initialize:function(t,i){p(this,t),this._source=i},onAdd:function(t){this._zoomAnimated=t._zoomAnimated,this._container||this._initLayout(),t._fadeAnimated&&vi(this._container,0),clearTimeout(this._removeTimeout),this.getPane().appendChild(this._container),this.update(),t._fadeAnimated&&vi(this._container,1),this.bringToFront()},onRemove:function(t){t._fadeAnimated?(vi(this._container,0),this._removeTimeout=setTimeout(a(ui,void 0,this._container),200)):ui(this._container)},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=W(t),this._map&&(this._updatePosition(),this._adjustPan()),this},getContent:function(){return this._content},setContent:function(t){return this._content=t,this.update(),this},getElement:function(){return this._container},update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},getEvents:function(){var t={zoom:this._updatePosition,viewreset:this._updatePosition};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},isOpen:function(){return!!this._map&&this._map.hasLayer(this)},bringToFront:function(){return this._map&&ci(this._container),this},bringToBack:function(){return this._map&&_i(this._container),this},_prepareOpen:function(t,i,e){if(i instanceof Se||(e=i,i=t),i instanceof Ee)for(var n in t._layers){i=t._layers[n];break}if(!e)if(i.getCenter)e=i.getCenter();else{if(!i.getLatLng)throw new Error("Unable to get source layer LatLng.");e=i.getLatLng()}return this._source=i,this.update(),e},_updateContent:function(){if(this._content){var t=this._contentNode,i="function"==typeof this._content?this._content(this._source||this):this._content;if("string"==typeof i)t.innerHTML=i;else{for(;t.hasChildNodes();)t.removeChild(t.firstChild);t.appendChild(i)}this.fire("contentupdate")}},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),i=I(this.options.offset),e=this._getAnchor();this._zoomAnimated?wi(this._container,t.add(e)):i=i.add(t).add(e);var n=this._containerBottom=-i.y,o=this._containerLeft=-Math.round(this._containerWidth/2)+i.x;this._container.style.bottom=n+"px",this._container.style.left=o+"px"}},_getAnchor:function(){return[0,0]}}),nn=en.extend({options:{maxWidth:300,minWidth:50,maxHeight:null,autoPan:!0,autoPanPaddingTopLeft:null,autoPanPaddingBottomRight:null,autoPanPadding:[5,5],keepInView:!1,closeButton:!0,autoClose:!0,closeOnEscapeKey:!0,className:""},openOn:function(t){return t.openPopup(this),this},onAdd:function(t){en.prototype.onAdd.call(this,t),t.fire("popupopen",{popup:this}),this._source&&(this._source.fire("popupopen",{popup:this},!0),this._source instanceof Oe||this._source.on("preclick",Oi))},onRemove:function(t){en.prototype.onRemove.call(this,t),t.fire("popupclose",{popup:this}),this._source&&(this._source.fire("popupclose",{popup:this},!0),this._source instanceof Oe||this._source.off("preclick",Oi))},getEvents:function(){var t=en.prototype.getEvents.call(this);return(void 0!==this.options.closeOnClick?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this._close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_close:function(){this._map&&this._map.closePopup(this)},_initLayout:function(){var t="leaflet-popup",i=this._container=hi("div",t+" "+(this.options.className||"")+" leaflet-zoom-animated"),e=this._wrapper=hi("div",t+"-content-wrapper",i);if(this._contentNode=hi("div",t+"-content",e),Ni(e),Ri(this._contentNode),Ei(e,"contextmenu",Oi),this._tipContainer=hi("div",t+"-tip-container",i),this._tip=hi("div",t+"-tip",this._tipContainer),this.options.closeButton){var n=this._closeButton=hi("a",t+"-close-button",i);n.href="#close",n.innerHTML="&#215;",Ei(n,"click",this._onCloseButtonClick,this)}},_updateLayout:function(){var t=this._contentNode,i=t.style;i.width="",i.whiteSpace="nowrap";var e=t.offsetWidth;e=Math.min(e,this.options.maxWidth),e=Math.max(e,this.options.minWidth),i.width=e+1+"px",i.whiteSpace="",i.height="";var n=t.offsetHeight,o=this.options.maxHeight,s="leaflet-popup-scrolled";o&&o<n?(i.height=o+"px",pi(t,s)):mi(t,s),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),e=this._getAnchor();wi(this._container,i.add(e))},_adjustPan:function(){if(this.options.autoPan){this._map._panAnim&&this._map._panAnim.stop();var t=this._map,i=parseInt(ai(this._container,"marginBottom"),10)||0,e=this._container.offsetHeight+i,n=this._containerWidth,o=new B(this._containerLeft,-e-this._containerBottom);o._add(Pi(this._container));var s=t.layerPointToContainerPoint(o),r=I(this.options.autoPanPadding),a=I(this.options.autoPanPaddingTopLeft||r),h=I(this.options.autoPanPaddingBottomRight||r),u=t.getSize(),l=0,c=0;s.x+n+h.x>u.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire("autopanstart").panBy([l,c])}},_onCloseButtonClick:function(t){this._close(),ji(t)},_getAnchor:function(){return I(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});Ji.mergeOptions({closePopupOnClick:!0}),Ji.include({openPopup:function(t,i,e){return t instanceof nn||(t=new nn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),Se.include({bindPopup:function(t,i){return t instanceof nn?(p(t,i),(this._popup=t)._source=this):(this._popup&&!i||(this._popup=new nn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){return this._popup&&this._map&&(i=this._popup._prepareOpen(this,t,i),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(ji(t),i instanceof Oe?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var on=en.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){en.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){en.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=en.prototype.getEvents.call(this);return Tt&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=hi("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i=this._map,e=this._container,n=i.latLngToContainerPoint(i.getCenter()),o=i.layerPointToContainerPoint(t),s=this.options.direction,r=e.offsetWidth,a=e.offsetHeight,h=I(this.options.offset),u=this._getAnchor();t="top"===s?t.add(I(-r/2+h.x,-a+h.y+u.y,!0)):"bottom"===s?t.subtract(I(r/2-h.x,-h.y,!0)):"center"===s?t.subtract(I(r/2+h.x,a/2-u.y+h.y,!0)):"right"===s||"auto"===s&&o.x<n.x?(s="right",t.add(I(h.x+u.x,u.y-a/2+h.y,!0))):(s="left",t.subtract(I(r+u.x-h.x,a/2-u.y-h.y,!0))),mi(e,"leaflet-tooltip-right"),mi(e,"leaflet-tooltip-left"),mi(e,"leaflet-tooltip-top"),mi(e,"leaflet-tooltip-bottom"),pi(e,"leaflet-tooltip-"+s),wi(e,t)},_updatePosition:function(){var t=this._map.latLngToLayerPoint(this._latlng);this._setPosition(t)},setOpacity:function(t){this.options.opacity=t,this._container&&vi(this._container,t)},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);this._setPosition(i)},_getAnchor:function(){return I(this._source&&this._source._getTooltipAnchor&&!this.options.sticky?this._source._getTooltipAnchor():[0,0])}});Ji.include({openTooltip:function(t,i,e){return t instanceof on||(t=new on(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:this.addLayer(t)},closeTooltip:function(t){return t&&this.removeLayer(t),this}}),Se.include({bindTooltip:function(t,i){return t instanceof on?(p(t,i),(this._tooltip=t)._source=this):(this._tooltip&&!i||(this._tooltip=new on(i,this)),this._tooltip.setContent(t)),this._initTooltipInteractions(),this._tooltip.options.permanent&&this._map&&this._map.hasLayer(this)&&this.openTooltip(),this},unbindTooltip:function(){return this._tooltip&&(this._initTooltipInteractions(!0),this.closeTooltip(),this._tooltip=null),this},_initTooltipInteractions:function(t){if(t||!this._tooltipHandlersAdded){var i=t?"off":"on",e={remove:this.closeTooltip,move:this._moveTooltip};this._tooltip.options.permanent?e.add=this._openTooltip:(e.mouseover=this._openTooltip,e.mouseout=this.closeTooltip,this._tooltip.options.sticky&&(e.mousemove=this._moveTooltip),Tt&&(e.click=this._openTooltip)),this[i](e),this._tooltipHandlersAdded=!t}},openTooltip:function(t,i){return this._tooltip&&this._map&&(i=this._tooltip._prepareOpen(this,t,i),this._map.openTooltip(this._tooltip,i),this._tooltip.options.interactive&&this._tooltip._container&&(pi(this._tooltip._container,"leaflet-clickable"),this.addInteractiveTarget(this._tooltip._container))),this},closeTooltip:function(){return this._tooltip&&(this._tooltip._close(),this._tooltip.options.interactive&&this._tooltip._container&&(mi(this._tooltip._container,"leaflet-clickable"),this.removeInteractiveTarget(this._tooltip._container))),this},toggleTooltip:function(t){return this._tooltip&&(this._tooltip._map?this.closeTooltip():this.openTooltip(t)),this},isTooltipOpen:function(){return this._tooltip.isOpen()},setTooltipContent:function(t){return this._tooltip&&this._tooltip.setContent(t),this},getTooltip:function(){return this._tooltip},_openTooltip:function(t){var i=t.layer||t.target;this._tooltip&&this._map&&this.openTooltip(i,this._tooltip.options.sticky?t.latlng:void 0)},_moveTooltip:function(t){var i,e,n=t.latlng;this._tooltip.options.sticky&&t.originalEvent&&(i=this._map.mouseEventToContainerPoint(t.originalEvent),e=this._map.containerPointToLayerPoint(i),n=this._map.layerPointToLatLng(e)),this._tooltip.setLatLng(n)}});var sn=ke.extend({options:{iconSize:[12,12],html:!1,bgPos:null,className:"leaflet-div-icon"},createIcon:function(t){var i=t&&"DIV"===t.tagName?t:document.createElement("div"),e=this.options;if(e.html instanceof Element?(li(i),i.appendChild(e.html)):i.innerHTML=!1!==e.html?e.html:"",e.bgPos){var n=I(e.bgPos);i.style.backgroundPosition=-n.x+"px "+-n.y+"px"}return this._setIconStyles(i,"icon"),i},createShadow:function(){return null}});ke.Default=Be;var rn=Se.extend({options:{tileSize:256,opacity:1,updateWhenIdle:xt,updateWhenZooming:!0,updateInterval:200,zIndex:1,bounds:null,minZoom:0,maxZoom:void 0,maxNativeZoom:void 0,minNativeZoom:void 0,noWrap:!1,pane:"tilePane",className:"",keepBuffer:2},initialize:function(t){p(this,t)},onAdd:function(){this._initContainer(),this._levels={},this._tiles={},this._resetView(),this._update()},beforeAdd:function(t){t._addZoomLimit(this)},onRemove:function(t){this._removeAllTiles(),ui(this._container),t._removeZoomLimit(this),this._container=null,this._tileZoom=void 0},bringToFront:function(){return this._map&&(ci(this._container),this._setAutoZIndex(Math.max)),this},bringToBack:function(){return this._map&&(_i(this._container),this._setAutoZIndex(Math.min)),this},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},isLoading:function(){return this._loading},redraw:function(){return this._map&&(this._removeAllTiles(),this._update()),this},getEvents:function(){var t={viewprereset:this._invalidateAll,viewreset:this._resetView,zoom:this._resetView,moveend:this._onMoveEnd};return this.options.updateWhenIdle||(this._onMove||(this._onMove=o(this._onMoveEnd,this.options.updateInterval,this)),t.move=this._onMove),this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},createTile:function(){return document.createElement("div")},getTileSize:function(){var t=this.options.tileSize;return t instanceof B?t:new B(t,t)},_updateZIndex:function(){this._container&&void 0!==this.options.zIndex&&null!==this.options.zIndex&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t){for(var i,e=this.getPane().children,n=-t(-1/0,1/0),o=0,s=e.length;o<s;o++)i=e[o].style.zIndex,e[o]!==this._container&&i&&(n=t(n,+i));isFinite(n)&&(this.options.zIndex=n+t(-1,1),this._updateZIndex())},_updateOpacity:function(){if(this._map&&!et){vi(this._container,this.options.opacity);var t=+new Date,i=!1,e=!1;for(var n in this._tiles){var o=this._tiles[n];if(o.current&&o.loaded){var s=Math.min(1,(t-o.loaded)/200);vi(o.el,s),s<1?i=!0:(o.active?e=!0:this._onOpaqueTile(o),o.active=!0)}}e&&!this._noPrune&&this._pruneTiles(),i&&(C(this._fadeFrame),this._fadeFrame=M(this._updateOpacity,this))}},_onOpaqueTile:l,_initContainer:function(){this._container||(this._container=hi("div","leaflet-layer "+(this.options.className||"")),this._updateZIndex(),this.options.opacity<1&&this._updateOpacity(),this.getPane().appendChild(this._container))},_updateLevels:function(){var t=this._tileZoom,i=this.options.maxZoom;if(void 0!==t){for(var e in this._levels)this._levels[e].el.children.length||e===t?(this._levels[e].el.style.zIndex=i-Math.abs(t-e),this._onUpdateLevel(e)):(ui(this._levels[e].el),this._removeTilesAtZoom(e),this._onRemoveLevel(e),delete this._levels[e]);var n=this._levels[t],o=this._map;return n||((n=this._levels[t]={}).el=hi("div","leaflet-tile-container leaflet-zoom-animated",this._container),n.el.style.zIndex=i,n.origin=o.project(o.unproject(o.getPixelOrigin()),t).round(),n.zoom=t,this._setZoomTransform(n,o.getCenter(),o.getZoom()),n.el.offsetWidth,this._onCreateLevel(n)),this._level=n}},_onUpdateLevel:l,_onRemoveLevel:l,_onCreateLevel:l,_pruneTiles:function(){if(this._map){var t,i,e=this._map.getZoom();if(e>this.options.maxZoom||e<this.options.minZoom)this._removeAllTiles();else{for(t in this._tiles)(i=this._tiles[t]).retain=i.current;for(t in this._tiles)if((i=this._tiles[t]).current&&!i.active){var n=i.coords;this._retainParent(n.x,n.y,n.z,n.z-5)||this._retainChildren(n.x,n.y,n.z,n.z+2)}for(t in this._tiles)this._tiles[t].retain||this._removeTile(t)}}},_removeTilesAtZoom:function(t){for(var i in this._tiles)this._tiles[i].coords.z===t&&this._removeTile(i)},_removeAllTiles:function(){for(var t in this._tiles)this._removeTile(t)},_invalidateAll:function(){for(var t in this._levels)ui(this._levels[t].el),this._onRemoveLevel(t),delete this._levels[t];this._removeAllTiles(),this._tileZoom=void 0},_retainParent:function(t,i,e,n){var o=Math.floor(t/2),s=Math.floor(i/2),r=e-1,a=new B(+o,+s);a.z=+r;var h=this._tileCoordsToKey(a),u=this._tiles[h];return u&&u.active?u.retain=!0:(u&&u.loaded&&(u.retain=!0),n<r&&this._retainParent(o,s,r,n))},_retainChildren:function(t,i,e,n){for(var o=2*t;o<2*t+2;o++)for(var s=2*i;s<2*i+2;s++){var r=new B(o,s);r.z=e+1;var a=this._tileCoordsToKey(r),h=this._tiles[a];h&&h.active?h.retain=!0:(h&&h.loaded&&(h.retain=!0),e+1<n&&this._retainChildren(o,s,e+1,n))}},_resetView:function(t){var i=t&&(t.pinch||t.flyTo);this._setView(this._map.getCenter(),this._map.getZoom(),i,i)},_animateZoom:function(t){this._setView(t.center,t.zoom,!0,t.noUpdate)},_clampZoom:function(t){var i=this.options;return void 0!==i.minNativeZoom&&t<i.minNativeZoom?i.minNativeZoom:void 0!==i.maxNativeZoom&&i.maxNativeZoom<t?i.maxNativeZoom:t},_setView:function(t,i,e,n){var o=this._clampZoom(Math.round(i));(void 0!==this.options.maxZoom&&o>this.options.maxZoom||void 0!==this.options.minZoom&&o<this.options.minZoom)&&(o=void 0);var s=this.options.updateWhenZooming&&o!==this._tileZoom;n&&!s||(this._tileZoom=o,this._abortLoading&&this._abortLoading(),this._updateLevels(),this._resetGrid(),void 0!==o&&this._update(t),e||this._pruneTiles(),this._noPrune=!!e),this._setZoomTransforms(t,i)},_setZoomTransforms:function(t,i){for(var e in this._levels)this._setZoomTransform(this._levels[e],t,i)},_setZoomTransform:function(t,i,e){var n=this._map.getZoomScale(e,t.zoom),o=t.origin.multiplyBy(n).subtract(this._map._getNewPixelOrigin(i,e)).round();yt?xi(t.el,o,n):wi(t.el,o)},_resetGrid:function(){var t=this._map,i=t.options.crs,e=this._tileSize=this.getTileSize(),n=this._tileZoom,o=this._map.getPixelWorldBounds(this._tileZoom);o&&(this._globalTileRange=this._pxBoundsToTileRange(o)),this._wrapX=i.wrapLng&&!this.options.noWrap&&[Math.floor(t.project([0,i.wrapLng[0]],n).x/e.x),Math.ceil(t.project([0,i.wrapLng[1]],n).x/e.y)],this._wrapY=i.wrapLat&&!this.options.noWrap&&[Math.floor(t.project([i.wrapLat[0],0],n).y/e.x),Math.ceil(t.project([i.wrapLat[1],0],n).y/e.y)]},_onMoveEnd:function(){this._map&&!this._map._animatingZoom&&this._update()},_getTiledPixelBounds:function(t){var i=this._map,e=i._animatingZoom?Math.max(i._animateToZoom,i.getZoom()):i.getZoom(),n=i.getZoomScale(e,this._tileZoom),o=i.project(t,this._tileZoom).floor(),s=i.getSize().divideBy(2*n);return new O(o.subtract(s),o.add(s))},_update:function(t){var i=this._map;if(i){var e=this._clampZoom(i.getZoom());if(void 0===t&&(t=i.getCenter()),void 0!==this._tileZoom){var n=this._getTiledPixelBounds(t),o=this._pxBoundsToTileRange(n),s=o.getCenter(),r=[],a=this.options.keepBuffer,h=new O(o.getBottomLeft().subtract([a,-a]),o.getTopRight().add([a,-a]));if(!(isFinite(o.min.x)&&isFinite(o.min.y)&&isFinite(o.max.x)&&isFinite(o.max.y)))throw new Error("Attempted to load an infinite number of tiles");for(var u in this._tiles){var l=this._tiles[u].coords;l.z===this._tileZoom&&h.contains(new B(l.x,l.y))||(this._tiles[u].current=!1)}if(1<Math.abs(e-this._tileZoom))this._setView(t,e);else{for(var c=o.min.y;c<=o.max.y;c++)for(var _=o.min.x;_<=o.max.x;_++){var d=new B(_,c);if(d.z=this._tileZoom,this._isValidTile(d)){var p=this._tiles[this._tileCoordsToKey(d)];p?p.current=!0:r.push(d)}}if(r.sort(function(t,i){return t.distanceTo(s)-i.distanceTo(s)}),0!==r.length){this._loading||(this._loading=!0,this.fire("loading"));var m=document.createDocumentFragment();for(_=0;_<r.length;_++)this._addTile(r[_],m);this._level.el.appendChild(m)}}}}},_isValidTile:function(t){var i=this._map.options.crs;if(!i.infinite){var e=this._globalTileRange;if(!i.wrapLng&&(t.x<e.min.x||t.x>e.max.x)||!i.wrapLat&&(t.y<e.min.y||t.y>e.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return D(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e);return[i.unproject(n,t.z),i.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var i=this._tileCoordsToNwSe(t),e=new N(i[0],i[1]);return this.options.noWrap||(e=this._map.wrapLatLngBounds(e)),e},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var i=t.split(":"),e=new B(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(ui(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){pi(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=l,t.onmousemove=l,et&&this.options.opacity<1&&vi(t,this.options.opacity),st&&!rt&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,i){var e=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&M(a(this._tileReady,this,t,null,o)),wi(o,e),this._tiles[n]={el:o,coords:t,current:!0},i.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,i,e){i&&this.fire("tileerror",{error:i,tile:e,coords:t});var n=this._tileCoordsToKey(t);(e=this._tiles[n])&&(e.loaded=+new Date,this._map._fadeAnimated?(vi(e.el,0),C(this._fadeFrame),this._fadeFrame=M(this._updateOpacity,this)):(e.active=!0,this._pruneTiles()),i||(pi(e.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:e.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),et||!this._map._fadeAnimated?M(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new B(this._wrapX?r(t.x,this._wrapX):t.x,this._wrapY?r(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new O(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var an=rn.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=p(this,i)).detectRetina&&Ct&&0<i.maxZoom&&(i.tileSize=Math.floor(i.tileSize/2),i.zoomReverse?(i.zoomOffset--,i.minZoom++):(i.zoomOffset++,i.maxZoom--),i.minZoom=Math.max(0,i.minZoom)),"string"==typeof i.subdomains&&(i.subdomains=i.subdomains.split("")),st||this.on("tileunload",this._onTileRemove)},setUrl:function(t,i){return this._url===t&&void 0===i&&(i=!0),this._url=t,i||this.redraw(),this},createTile:function(t,i){var e=document.createElement("img");return Ei(e,"load",a(this._tileOnLoad,this,i,e)),Ei(e,"error",a(this._tileOnError,this,i,e)),!this.options.crossOrigin&&""!==this.options.crossOrigin||(e.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),e.alt="",e.setAttribute("role","presentation"),e.src=this.getTileUrl(t),e},getTileUrl:function(t){var i={r:Ct?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var e=this._globalTileRange.max.y-t.y;this.options.tms&&(i.y=e),i["-y"]=e}return g(this._url,h(i,this.options))},_tileOnLoad:function(t,i){et?setTimeout(a(t,this,null,i),0):t(null,i)},_tileOnError:function(t,i,e){var n=this.options.errorTileUrl;n&&i.getAttribute("src")!==n&&(i.src=n),t(e,i)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,i=this.options.maxZoom;return this.options.zoomReverse&&(t=i-t),t+this.options.zoomOffset},_getSubdomain:function(t){var i=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[i]},_abortLoading:function(){var t,i;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&((i=this._tiles[t].el).onload=l,i.onerror=l,i.complete||(i.src=x,ui(i),delete this._tiles[t]))},_removeTile:function(t){var i=this._tiles[t];if(i)return ht||i.el.setAttribute("src",x),rn.prototype._removeTile.call(this,t)},_tileReady:function(t,i,e){if(this._map&&(!e||e.getAttribute("src")!==x))return rn.prototype._tileReady.call(this,t,i,e)}});function hn(t,i){return new an(t,i)}var un=an.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,i){this._url=t;var e=h({},this.defaultWmsParams);for(var n in i)n in this.options||(e[n]=i[n]);var o=(i=p(this,i)).detectRetina&&Ct?2:1,s=this.getTileSize();e.width=s.x*o,e.height=s.y*o,this.wmsParams=e},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var i=1.3<=this._wmsVersion?"crs":"srs";this.wmsParams[i]=this._crs.code,an.prototype.onAdd.call(this,t)},getTileUrl:function(t){var i=this._tileCoordsToNwSe(t),e=this._crs,n=R(e.project(i[0]),e.project(i[1])),o=n.min,s=n.max,r=(1.3<=this._wmsVersion&&this._crs===Me?[o.y,o.x,s.y,s.x]:[o.x,o.y,s.x,s.y]).join(","),a=an.prototype.getTileUrl.call(this,t);return a+m(this.wmsParams,a,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+r},setParams:function(t,i){return h(this.wmsParams,t),i||this.redraw(),this}});an.WMS=un,hn.wms=function(t,i){return new un(t,i)};var ln=Se.extend({options:{padding:.1,tolerance:0},initialize:function(t){p(this,t),u(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),this._zoomAnimated&&pi(this._container,"leaflet-zoom-animated")),this.getPane().appendChild(this._container),this._update(),this.on("update",this._updatePaths,this)},onRemove:function(){this.off("update",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,i){var e=this._map.getZoomScale(i,this._zoom),n=Pi(this._container),o=this._map.getSize().multiplyBy(.5+this.options.padding),s=this._map.project(this._center,i),r=this._map.project(t,i).subtract(s),a=o.multiplyBy(-e).add(n).add(o).subtract(r);yt?xi(this._container,a,e):wi(this._container,a)},_reset:function(){for(var t in this._update(),this._updateTransform(this._center,this._zoom),this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,i=this._map.getSize(),e=this._map.containerPointToLayerPoint(i.multiplyBy(-t)).round();this._bounds=new O(e,e.add(i.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),cn=ln.extend({getEvents:function(){var t=ln.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){ln.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement("canvas");Ei(t,"mousemove",o(this._onMouseMove,32,this),this),Ei(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this),Ei(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_destroyContainer:function(){C(this._redrawRequest),delete this._ctx,ui(this._container),Bi(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){for(var t in this._redrawBounds=null,this._layers)this._layers[t]._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){ln.prototype._update.call(this);var t=this._bounds,i=this._container,e=t.getSize(),n=Ct?2:1;wi(i,t.min),i.width=n*e.x,i.height=n*e.y,i.style.width=e.x+"px",i.style.height=e.y+"px",Ct&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_reset:function(){ln.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t);var i=(this._layers[u(t)]=t)._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=i),this._drawLast=i,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var i=t._order,e=i.next,n=i.prev;e?e.prev=n:this._drawLast=n,n?n.next=e:this._drawFirst=e,delete t._order,delete this._layers[u(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if("string"==typeof t.options.dashArray){var i,e,n=t.options.dashArray.split(/[, ]+/),o=[];for(e=0;e<n.length;e++){if(i=Number(n[e]),isNaN(i))return;o.push(i)}t.options._dashArray=o}else t.options._dashArray=t.options.dashArray},_requestRedraw:function(t){this._map&&(this._extendRedrawBounds(t),this._redrawRequest=this._redrawRequest||M(this._redraw,this))},_extendRedrawBounds:function(t){if(t._pxBounds){var i=(t.options.weight||0)+1;this._redrawBounds=this._redrawBounds||new O,this._redrawBounds.extend(t._pxBounds.min.subtract([i,i])),this._redrawBounds.extend(t._pxBounds.max.add([i,i]))}},_redraw:function(){this._redrawRequest=null,this._redrawBounds&&(this._redrawBounds.min._floor(),this._redrawBounds.max._ceil()),this._clear(),this._draw(),this._redrawBounds=null},_clear:function(){var t=this._redrawBounds;if(t){var i=t.getSize();this._ctx.clearRect(t.min.x,t.min.y,i.x,i.y)}else this._ctx.clearRect(0,0,this._container.width,this._container.height)},_draw:function(){var t,i=this._redrawBounds;if(this._ctx.save(),i){var e=i.getSize();this._ctx.beginPath(),this._ctx.rect(i.min.x,i.min.y,e.x,e.y),this._ctx.clip()}this._drawing=!0;for(var n=this._drawFirst;n;n=n.next)t=n.layer,(!i||t._pxBounds&&t._pxBounds.intersects(i))&&t._updatePath();this._drawing=!1,this._ctx.restore()},_updatePoly:function(t,i){if(this._drawing){var e,n,o,s,r=t._parts,a=r.length,h=this._ctx;if(a){for(h.beginPath(),e=0;e<a;e++){for(n=0,o=r[e].length;n<o;n++)s=r[e][n],h[n?"lineTo":"moveTo"](s.x,s.y);i&&h.closePath()}this._fillStroke(h,t)}}},_updateCircle:function(t){if(this._drawing&&!t._empty()){var i=t._point,e=this._ctx,n=Math.max(Math.round(t._radius),1),o=(Math.max(Math.round(t._radiusY),1)||n)/n;1!=o&&(e.save(),e.scale(1,o)),e.beginPath(),e.arc(i.x,i.y/o,n,0,2*Math.PI,!1),1!=o&&e.restore(),this._fillStroke(e,t)}},_fillStroke:function(t,i){var e=i.options;e.fill&&(t.globalAlpha=e.fillOpacity,t.fillStyle=e.fillColor||e.color,t.fill(e.fillRule||"evenodd")),e.stroke&&0!==e.weight&&(t.setLineDash&&t.setLineDash(i.options&&i.options._dashArray||[]),t.globalAlpha=e.opacity,t.lineWidth=e.weight,t.strokeStyle=e.color,t.lineCap=e.lineCap,t.lineJoin=e.lineJoin,t.stroke())},_onClick:function(t){for(var i,e,n=this._map.mouseEventToLayerPoint(t),o=this._drawFirst;o;o=o.next)(i=o.layer).options.interactive&&i._containsPoint(n)&&!this._map._draggableMoved(i)&&(e=i);e&&(qi(t),this._fireEvent([e],t))},_onMouseMove:function(t){if(this._map&&!this._map.dragging.moving()&&!this._map._animatingZoom){var i=this._map.mouseEventToLayerPoint(t);this._handleMouseHover(t,i)}},_handleMouseOut:function(t){var i=this._hoveredLayer;i&&(mi(this._container,"leaflet-interactive"),this._fireEvent([i],t,"mouseout"),this._hoveredLayer=null)},_handleMouseHover:function(t,i){for(var e,n,o=this._drawFirst;o;o=o.next)(e=o.layer).options.interactive&&e._containsPoint(i)&&(n=e);n!==this._hoveredLayer&&(this._handleMouseOut(t),n&&(pi(this._container,"leaflet-interactive"),this._fireEvent([n],t,"mouseover"),this._hoveredLayer=n)),this._hoveredLayer&&this._fireEvent([this._hoveredLayer],t)},_fireEvent:function(t,i,e){this._map._fireDOMEvent(i,e||i.type,t)},_bringToFront:function(t){var i=t._order;if(i){var e=i.next,n=i.prev;e&&((e.prev=n)?n.next=e:e&&(this._drawFirst=e),i.prev=this._drawLast,(this._drawLast.next=i).next=null,this._drawLast=i,this._requestRedraw(t))}},_bringToBack:function(t){var i=t._order;if(i){var e=i.next,n=i.prev;n&&((n.next=e)?e.prev=n:n&&(this._drawLast=n),i.prev=null,i.next=this._drawFirst,this._drawFirst.prev=i,this._drawFirst=i,this._requestRedraw(t))}}});function _n(t){return St?new cn(t):null}var dn=function(){try{return document.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return document.createElement("<lvml:"+t+' class="lvml">')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),pn={_initContainer:function(){this._container=hi("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(ln.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=dn("shape");pi(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=dn("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[u(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;ui(i),t.removeInteractiveTarget(i),delete this._layers[u(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i||(i=t._stroke=dn("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=v(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e||(e=t._fill=dn("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){ci(t._container)},_bringToBack:function(t){_i(t._container)}},mn=Et?dn:$,fn=ln.extend({getEvents:function(){var t=ln.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=mn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=mn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){ui(this._container),Bi(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){ln.prototype._update.call(this);var t=this._bounds,i=t.getSize(),e=this._container;this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),wi(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update")}},_initPath:function(t){var i=t._path=mn("path");t.options.className&&pi(i,t.options.className),t.options.interactive&&pi(i,"leaflet-interactive"),this._updateStyle(t),this._layers[u(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){ui(t._path),t.removeInteractiveTarget(t._path),delete this._layers[u(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute("stroke",e.color),i.setAttribute("stroke-opacity",e.opacity),i.setAttribute("stroke-width",e.weight),i.setAttribute("stroke-linecap",e.lineCap),i.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?i.setAttribute("stroke-dasharray",e.dashArray):i.removeAttribute("stroke-dasharray"),e.dashOffset?i.setAttribute("stroke-dashoffset",e.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),e.fill?(i.setAttribute("fill",e.fillColor||e.color),i.setAttribute("fill-opacity",e.fillOpacity),i.setAttribute("fill-rule",e.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,Q(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){ci(t._path)},_bringToBack:function(t){_i(t._path)}});function gn(t){return Zt||Et?new fn(t):null}Et&&fn.include(pn),Ji.include({getRenderer:function(t){var i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return i||(i=this._renderer=this._createRenderer()),this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&_n(t)||gn(t)}});var vn=je.extend({initialize:function(t,i){je.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=D(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});fn.create=mn,fn.pointsToPath=Q,We.geometryToLayer=He,We.coordsToLatLng=Fe,We.coordsToLatLngs=Ue,We.latLngToCoords=Ve,We.latLngsToCoords=qe,We.getFeature=Ge,We.asFeature=Ke,Ji.mergeOptions({boxZoom:!0});var yn=oe.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){Ei(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){Bi(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){ui(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),$t(),Li(),this._startPoint=this._map.mouseEventToContainerPoint(t),Ei(document,{contextmenu:ji,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=hi("div","leaflet-zoom-box",this._container),pi(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var i=new O(this._point,this._startPoint),e=i.getSize();wi(this._box,i.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(ui(this._box),mi(this._container,"leaflet-crosshair")),Qt(),Ti(),Bi(document,{contextmenu:ji,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0);var i=new N(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(i).fire("boxzoomend",{boxZoomBounds:i})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});Ji.addInitHook("addHandler","boxZoom",yn),Ji.mergeOptions({doubleClickZoom:!0});var xn=oe.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});Ji.addInitHook("addHandler","doubleClickZoom",xn),Ji.mergeOptions({dragging:!0,inertia:!rt,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var wn=oe.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new le(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}pi(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){mi(this._map._container,"leaflet-grab"),mi(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var i=D(this._map.options.maxBounds);this._offsetLimit=R(this._map.latLngToContainerPoint(i.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(i.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(e),this._times.push(i),this._prunePositions(i)}this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1<this._positions.length&&50<t-this._times[0];)this._positions.shift(),this._times.shift()},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),i=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=i.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,i){return t-(t-i)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),i=this._offsetLimit;t.x<i.min.x&&(t.x=this._viscousLimit(t.x,i.min.x)),t.y<i.min.y&&(t.y=this._viscousLimit(t.y,i.min.y)),t.x>i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)<Math.abs(s+e)?o:s;this._draggable._absPos=this._draggable._newPos.clone(),this._draggable._newPos.x=r},_onDragEnd:function(t){var i=this._map,e=i.options,n=!e.inertia||this._times.length<2;if(i.fire("dragend",t),n)i.fire("moveend");else{this._prunePositions(+new Date);var o=this._lastPos.subtract(this._positions[0]),s=(this._lastTime-this._times[0])/1e3,r=e.easeLinearity,a=o.multiplyBy(r/s),h=a.distanceTo([0,0]),u=Math.min(e.inertiaMaxSpeed,h),l=a.multiplyBy(u/h),c=u/(e.inertiaDeceleration*r),_=l.multiplyBy(-c/2).round();_.x||_.y?(_=i._limitOffset(_,i.options.maxBounds),M(function(){i.panBy(_,{duration:c,easeLinearity:r,noMoveStart:!0,animate:!0})})):i.fire("moveend")}}});Ji.addInitHook("addHandler","dragging",wn),Ji.mergeOptions({keyboard:!0,keyboardPanDelta:80});var Pn=oe.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,54,173]},initialize:function(t){this._map=t,this._setPanDelta(t.options.keyboardPanDelta),this._setZoomDelta(t.options.zoomDelta)},addHooks:function(){var t=this._map._container;t.tabIndex<=0&&(t.tabIndex="0"),Ei(t,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.on({focus:this._addHooks,blur:this._removeHooks},this)},removeHooks:function(){this._removeHooks(),Bi(this._map._container,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.off({focus:this._addHooks,blur:this._removeHooks},this)},_onMouseDown:function(){if(!this._focused){var t=document.body,i=document.documentElement,e=t.scrollTop||i.scrollTop,n=t.scrollLeft||i.scrollLeft;this._map._container.focus(),window.scrollTo(n,e)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanDelta:function(t){var i,e,n=this._panKeys={},o=this.keyCodes;for(i=0,e=o.left.length;i<e;i++)n[o.left[i]]=[-1*t,0];for(i=0,e=o.right.length;i<e;i++)n[o.right[i]]=[t,0];for(i=0,e=o.down.length;i<e;i++)n[o.down[i]]=[0,t];for(i=0,e=o.up.length;i<e;i++)n[o.up[i]]=[0,-1*t]},_setZoomDelta:function(t){var i,e,n=this._zoomKeys={},o=this.keyCodes;for(i=0,e=o.zoomIn.length;i<e;i++)n[o.zoomIn[i]]=t;for(i=0,e=o.zoomOut.length;i<e;i++)n[o.zoomOut[i]]=-t},_addHooks:function(){Ei(document,"keydown",this._onKeyDown,this)},_removeHooks:function(){Bi(document,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){if(!(t.altKey||t.ctrlKey||t.metaKey)){var i,e=t.keyCode,n=this._map;if(e in this._panKeys)n._panAnim&&n._panAnim._inProgress||(i=this._panKeys[e],t.shiftKey&&(i=I(i).multiplyBy(3)),n.panBy(i),n.options.maxBounds&&n.panInsideBounds(n.options.maxBounds));else if(e in this._zoomKeys)n.setZoom(n.getZoom()+(t.shiftKey?3:1)*this._zoomKeys[e]);else{if(27!==e||!n._popup||!n._popup.options.closeOnEscapeKey)return;n.closePopup()}ji(t)}}});Ji.addInitHook("addHandler","keyboard",Pn),Ji.mergeOptions({scrollWheelZoom:!0,wheelDebounceTime:40,wheelPxPerZoomLevel:60});var bn=oe.extend({addHooks:function(){Ei(this._map._container,"mousewheel",this._onWheelScroll,this),this._delta=0},removeHooks:function(){Bi(this._map._container,"mousewheel",this._onWheelScroll,this)},_onWheelScroll:function(t){var i=Fi(t),e=this._map.options.wheelDebounceTime;this._delta+=i,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var n=Math.max(e-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(a(this._performZoom,this),n),ji(t)},_performZoom:function(){var t=this._map,i=t.getZoom(),e=this._map.options.zoomSnap||0;t._stop();var n=this._delta/(4*this._map.options.wheelPxPerZoomLevel),o=4*Math.log(2/(1+Math.exp(-Math.abs(n))))/Math.LN2,s=e?Math.ceil(o/e)*e:o,r=t._limitZoom(i+(0<this._delta?s:-s))-i;this._delta=0,this._startTime=null,r&&("center"===t.options.scrollWheelZoom?t.setZoom(i+r):t.setZoomAround(this._lastMousePos,i+r))}});Ji.addInitHook("addHandler","scrollWheelZoom",bn),Ji.mergeOptions({tap:!0,tapTolerance:15});var Ln=oe.extend({addHooks:function(){Ei(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){Bi(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(Di(t),this._fireClick=!0,1<t.touches.length)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],e=i.target;this._startPos=this._newPos=new B(i.clientX,i.clientY),e.tagName&&"a"===e.tagName.toLowerCase()&&pi(e,"leaflet-active"),this._holdTimeout=setTimeout(a(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),this._simulateEvent("mousedown",i),Ei(document,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),Bi(document,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],e=i.target;e&&e.tagName&&"a"===e.tagName.toLowerCase()&&mi(e,"leaflet-active"),this._simulateEvent("mouseup",i),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var i=t.touches[0];this._newPos=new B(i.clientX,i.clientY),this._simulateEvent("mousemove",i)},_simulateEvent:function(t,i){var e=document.createEvent("MouseEvents");e._simulated=!0,i.target._simulatedClick=!0,e.initMouseEvent(t,!0,!0,window,1,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),i.target.dispatchEvent(e)}});Tt&&!Lt&&Ji.addInitHook("addHandler","tap",Ln),Ji.mergeOptions({touchZoom:Tt&&!rt,bounceAtZoomLimits:!0});var Tn=oe.extend({addHooks:function(){pi(this._map._container,"leaflet-touch-zoom"),Ei(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){mi(this._map._container,"leaflet-touch-zoom"),Bi(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var e=i.mouseEventToContainerPoint(t.touches[0]),n=i.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=i.getSize()._divideBy(2),this._startLatLng=i.containerPointToLatLng(this._centerPoint),"center"!==i.options.touchZoom&&(this._pinchStartLatLng=i.containerPointToLatLng(e.add(n)._divideBy(2))),this._startDist=e.distanceTo(n),this._startZoom=i.getZoom(),this._moved=!1,this._zooming=!0,i._stop(),Ei(document,"touchmove",this._onTouchMove,this),Ei(document,"touchend",this._onTouchEnd,this),Di(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var i=this._map,e=i.mouseEventToContainerPoint(t.touches[0]),n=i.mouseEventToContainerPoint(t.touches[1]),o=e.distanceTo(n)/this._startDist;if(this._zoom=i.getScaleZoom(o,this._startZoom),!i.options.bounceAtZoomLimits&&(this._zoom<i.getMinZoom()&&o<1||this._zoom>i.getMaxZoom()&&1<o)&&(this._zoom=i._limitZoom(this._zoom)),"center"===i.options.touchZoom){if(this._center=this._startLatLng,1==o)return}else{var s=e._add(n)._divideBy(2)._subtract(this._centerPoint);if(1==o&&0===s.x&&0===s.y)return;this._center=i.unproject(i.project(this._pinchStartLatLng,this._zoom).subtract(s),this._zoom)}this._moved||(i._moveStart(!0,!1),this._moved=!0),C(this._animRequest);var r=a(i._move,i,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=M(r,this,!0),Di(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,C(this._animRequest),Bi(document,"touchmove",this._onTouchMove),Bi(document,"touchend",this._onTouchEnd),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}});Ji.addInitHook("addHandler","touchZoom",Tn),Ji.BoxZoom=yn,Ji.DoubleClickZoom=xn,Ji.Drag=wn,Ji.Keyboard=Pn,Ji.ScrollWheelZoom=bn,Ji.Tap=Ln,Ji.TouchZoom=Tn,Object.freeze=i,t.version="1.5.1+build.2e3e0ffb",t.Control=Qi,t.control=$i,t.Browser=Bt,t.Evented=k,t.Mixin=re,t.Util=S,t.Class=Z,t.Handler=oe,t.extend=h,t.bind=a,t.stamp=u,t.setOptions=p,t.DomEvent=Yi,t.DomUtil=Zi,t.PosAnimation=Xi,t.Draggable=le,t.LineUtil=ye,t.PolyUtil=Pe,t.Point=B,t.point=I,t.Bounds=O,t.bounds=R,t.Transformation=G,t.transformation=K,t.Projection=Te,t.LatLng=j,t.latLng=W,t.LatLngBounds=N,t.latLngBounds=D,t.CRS=F,t.GeoJSON=We,t.geoJSON=Xe,t.geoJson=Je,t.Layer=Se,t.LayerGroup=Ze,t.layerGroup=function(t,i){return new Ze(t,i)},t.FeatureGroup=Ee,t.featureGroup=function(t){return new Ee(t)},t.ImageOverlay=$e,t.imageOverlay=function(t,i,e){return new $e(t,i,e)},t.VideoOverlay=Qe,t.videoOverlay=function(t,i,e){return new Qe(t,i,e)},t.SVGOverlay=tn,t.svgOverlay=function(t,i,e){return new tn(t,i,e)},t.DivOverlay=en,t.Popup=nn,t.popup=function(t,i){return new nn(t,i)},t.Tooltip=on,t.tooltip=function(t,i){return new on(t,i)},t.Icon=ke,t.icon=function(t){return new ke(t)},t.DivIcon=sn,t.divIcon=function(t){return new sn(t)},t.Marker=Ie,t.marker=function(t,i){return new Ie(t,i)},t.TileLayer=an,t.tileLayer=hn,t.GridLayer=rn,t.gridLayer=function(t){return new rn(t)},t.SVG=fn,t.svg=gn,t.Renderer=ln,t.Canvas=cn,t.canvas=_n,t.Path=Oe,t.CircleMarker=Re,t.circleMarker=function(t,i){return new Re(t,i)},t.Circle=Ne,t.circle=function(t,i,e){return new Ne(t,i,e)},t.Polyline=De,t.polyline=function(t,i){return new De(t,i)},t.Polygon=je,t.polygon=function(t,i){return new je(t,i)},t.Rectangle=vn,t.rectangle=function(t,i){return new vn(t,i)},t.Map=Ji,t.map=function(t,i){return new Ji(t,i)};var zn=window.L;t.noConflict=function(){return window.L=zn,this},window.L=t}); \ No newline at end of file
diff --git a/public/static/v109 b/public/static/v109
new file mode 120000
index 0000000..945c9b4
--- /dev/null
+++ b/public/static/v109
@@ -0,0 +1 @@
+. \ No newline at end of file
diff --git a/public/static/v110 b/public/static/v110
new file mode 120000
index 0000000..945c9b4
--- /dev/null
+++ b/public/static/v110
@@ -0,0 +1 @@
+. \ No newline at end of file
diff --git a/sass/app.scss b/sass/app.scss
new file mode 100644
index 0000000..75074bd
--- /dev/null
+++ b/sass/app.scss
@@ -0,0 +1,1153 @@
+/*
+ * Copyright (C) 2011-2020 Birte Kristina Friesel
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+body {
+ margin: 0;
+ color: $fg;
+ background-color: $bg;
+}
+
+html {
+ font-family: "Arimo", "Arial", Sans-Serif;
+}
+
+a {
+ color: $link-color;
+ text-decoration: none;
+}
+
+.visually-hidden {
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+}
+
+p,
+div.about,
+div.config,
+div.input-field,
+div.notes {
+ max-width: 94%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+div.journey,
+div.nextstop {
+ max-width: 98%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+p {
+ text-align: justify;
+}
+
+div.content {
+ width: 100%;
+ margin: 0;
+}
+
+.copyright {
+ margin-top: 1em;
+ color: $fg3;
+ clear: both;
+}
+
+.wagonorder {
+ position: relative;
+ width: 100%;
+ height: 100ex;
+
+ &.exit-unknown {
+ .section {
+ left: 1em;
+ width: 2em;
+ }
+ .wagon {
+ left: 3em;
+ min-width: 6em;
+ }
+ .details {
+ left: 10em;
+ right: 0em;
+ }
+ }
+
+ &.exit-left {
+ .section {
+ left: 1em;
+ width: 2em;
+ background-color: $bg05;
+ }
+ .wagon {
+ left: 3em;
+ min-width: 6em;
+ }
+ .details {
+ left: 10em;
+ right: 0em;
+ }
+ }
+
+ &.exit-right {
+ .section {
+ right: 1em;
+ width: 2em;
+ background-color: $bg05;
+ }
+ .wagon {
+ right: 3em;
+ min-width: 6em;
+ }
+ .details {
+ right: 10em;
+ left: 0em;
+ text-align: right;
+ }
+ }
+
+ .section {
+ position: absolute;
+ text-align: center;
+ }
+
+ .wagon {
+ position: absolute;
+ border: 1px solid $fg3;
+ padding-left: 0.2em;
+ padding-right: 0.2em;
+
+ .material-icons {
+ color: $fg2;
+ }
+
+ .direction {
+ position: absolute;
+ left: 0.2em;
+ bottom: 0;
+ right: 0;
+ text-align: center;
+ color: $fg2;
+ }
+ }
+
+ .wagon ~ .wagon {
+ border-top: none;
+ }
+
+ .firstclass {
+ background-color: $firstclass-wagon-color;
+ }
+
+ .powercar {
+ background-color: $powercar-wagon-color;
+ }
+
+ .closed {
+ background-color: $closed-wagon-color;
+ }
+
+ .nondestwagon {
+ border-style: dashed;
+ }
+
+ .details {
+ position: absolute;
+ padding-top: 0.5ex;
+
+ .type {
+ display: inline-block;
+ width: 5em;
+ color: $fg;
+ }
+
+ a.type {
+ color: $link-color;
+ }
+
+ .groupno {
+ color: $fg;
+ }
+
+ .grouptype {
+ color: $fg2;
+ }
+
+ .grouptype:before {
+ content: "(";
+ }
+
+ .grouptype:after {
+ content: ")";
+ }
+
+ .uicunknown {
+ color: $fg3;
+ }
+
+ .uicexchange {
+ margin-right: 0.2em;
+ color: $fg3;
+ }
+
+ .uiccountry {
+ margin-right: 0.2em;
+ color: $fg3;
+ }
+
+ .uic5 {
+ margin-right: 0.2em;
+ color: $fg3;
+ }
+
+ .uic56 {
+ color: $fg2;
+ font-weight: bold;
+ }
+
+ .uic78 {
+ margin-right: 0.2em;
+ color: $fg2;
+ font-weight: bold;
+ }
+
+ .uic78:before {
+ content: "-";
+ }
+
+ .uictype {
+ margin-right: 0.2em;
+ color: $fg2;
+ font-weight: bold;
+ }
+
+ .uicno {
+ color: $fg2;
+ }
+
+ .uiccheck {
+ color: $fg3;
+ }
+
+ .uiccheck:before {
+ content: "-";
+ }
+ }
+}
+
+.singlewagon {
+ .sign-left {
+ float: left;
+ padding-left: 5%;
+ }
+ .sign-right {
+ float: right;
+ padding-right: 5%;
+ }
+ .sign-center {
+ text-align: center;
+ }
+ .platform {
+ text-align: center;
+ background-color: $bg1;
+ font-weight: bold;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ }
+ img.wagonfile {
+ width: 100%;
+ margin-top: 0.2em;
+ margin-bottom: 0.2em;
+ }
+}
+
+div.app {
+ border-width:1px 2px;
+ width:100%;
+ margin-bottom: 5em;
+
+ > ul {
+ position:relative;
+ width:100%;
+
+ list-style-type:none;
+ margin:0;
+ padding:0;
+
+ > li {
+ min-height:7em;
+ display:block;
+ width:100%;
+ position:relative;
+ border-bottom: 1px solid $li-border-color;
+ background-color: $bg;
+
+ &.cancelled {
+ background-color: $cancelled-bg-color;
+ .time {
+ color: $fg !important;
+ }
+ }
+
+ &.past {
+ opacity: 0.8;
+ background-color: $past-bg-color;
+ }
+
+ > a {
+ color:$fg;
+ }
+
+ .anchor {
+ position: relative;
+ top: -12em;
+ }
+
+ .line {
+ font-size: 2.7em;
+ position:absolute;
+ bottom:5px;
+ left:2px;
+ max-width: 6em;
+ max-height: 3ex;
+ overflow: hidden;
+
+ .trainno {
+ font-weight: normal;
+ }
+
+ .trainno_sub {
+ font-weight: normal;
+ font-size: 0.6em;
+ text-align: center;
+ margin-top: -0.2em;
+ }
+ }
+
+ .sbahn .trainno_sub {
+ font-weight: normal;
+ font-size: 0.5em;
+ text-align: center;
+ margin-top: -0.25em;
+ }
+
+ .lineinfo {
+ color:$fg;
+ font-size: 2em;
+ position:absolute;
+ top:0px;
+ left:2px;
+ }
+
+ .route, .info {
+ background-color: transparent;
+ font-size:2.1em;
+ position:absolute;
+ top: 0;
+ left: 7.7em;
+ right: 7em;
+ height: 1.5em;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+
+ .route {
+ color: $route-color;
+ }
+
+ .info {
+ color: $info-color;
+ }
+
+ .dest, .origin {
+ background-color: transparent;
+ font-size:4em;
+ position:absolute;
+ bottom:0;
+ left:4em;
+ width: 70%;
+ white-space: nowrap;
+ overflow: hidden;
+ color: $fg;
+ }
+
+ .dest {
+ background-color: transparent;
+ color: $fg;
+ }
+
+ .origin {
+ background-color: transparent;
+ color: $fg2;
+
+ &:before {
+ content: "von ";
+ }
+ }
+
+ .load {
+ color: $fg;
+ font-weight: normal;
+ margin-right: 0.5em;
+ }
+
+ .platform {
+ background-color: transparent;
+ font-size: 3em;
+ font-weight: bold;
+ position: absolute;
+ right: 5px;
+ bottom: 0;
+ padding-left: 0.2em;
+ color: $fg;
+ }
+
+ .changed-platform {
+ color: $info-color;
+ }
+
+
+ .time {
+ background-color: transparent;
+ font-size:2.3em;
+ position:absolute;
+ right:5px;
+ top:1px;
+ padding-left: 0.2em;
+ color: $fg;
+
+ &.delayed {
+ color: $delay-color;
+ background-color: transparent;
+ }
+
+ &.a-bit-delayed {
+ color: $smalldelay-color;
+ background-color: transparent;
+ }
+
+ &.on-time {
+ color: $ontime-color;
+ background-color: transparent;
+ }
+
+ .no-realtime {
+ background-color: transparent;
+ padding-right: 1ex;
+ i.material-icons {
+ font-size: 12px;
+ }
+ }
+
+ .delay {
+ font-size:1em;
+ color: $delay-color;
+ background-color: transparent;
+ padding-right: 1ex;
+ }
+
+ .undelay {
+ font-size:1em;
+ color: $undelay-color;
+ padding-right: 1ex;
+ }
+
+ .delaynorm {
+ font-size:0.9em;
+ color: $delaynorm-color;
+ }
+
+ .undelaynorm {
+ font-size:0.9em;
+ color: $undelaynorm-color;
+ }
+ }
+ }
+ }
+
+ .trainsubtype {
+ font-weight: normal;
+ font-size: 70%;
+ position: relative;
+ vertical-align: baseline;
+ top: -0.6ex;
+ left: -0.5ex;
+ }
+
+ .replacement {
+ color: $replacement-color;
+ }
+
+ .replaced {
+ color: $replaced-color;
+ }
+
+ .sbahn {
+ font-weight:bold;
+ border-radius: 30px;
+ padding:3px 6px 2px 6px;
+ background-color: $sbahn-color;
+ }
+
+ .bahn, .fern, .ext {
+ font-weight:bold;
+ border-radius: 5px;
+ padding:3px 5px 2px 5px;
+ }
+
+ .bahn {
+ background-color: $bahn-color;
+ }
+
+ .fern {
+ background-color: $fern-color;
+ }
+
+ .ext {
+ border: 2px solid $bahn-color;
+ }
+
+ .tram, .bus, .ubahn {
+ padding:3px 5px 2px 5px;
+ }
+
+ .tram {
+ background-color: $tram-color;
+ }
+
+ .bus {
+ background-color: $bus-color;
+ }
+
+ .ubahn {
+ background-color: $ubahn-color;
+ }
+
+ .moreinfo {
+ font-size:2.1em;
+ position:fixed;
+ left:0;
+ right:0;
+ bottom:0em;
+ z-index: 5;
+ overflow: auto;
+ cursor: default;
+ background-color: $bg;
+
+ .mheader, .mfooter {
+ max-width: 50em;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ .mheader {
+ text-align: center;
+ font-size: 120%;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ padding-left: 1em;
+ padding-right: 1em;
+ border-bottom: 0.1em dashed #cccccc;
+ }
+
+ .mfooter {
+ padding-top: 0.5em;
+ padding-left: 1em;
+ padding-right: 1em;
+ }
+
+ .dataline {
+ font-size: 120%;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 0.5em;
+
+ > div {
+ width: 33%;
+ }
+ }
+
+ .wagonorder-preview {
+ font-size: 110%;
+ width: 100%;
+ text-align: center;
+ margin-bottom: 1em;
+
+ a {
+ color: $fg;
+ }
+
+ .otherno {
+ color: $fg2;
+ }
+
+ .meta {
+ color: $fg1;
+ }
+ }
+
+ .departure {
+ text-align: right;
+ }
+
+ .platform {
+ text-align: center;
+ }
+
+ .arrival {
+ display: inline-block;
+ text-align: right;
+ }
+
+ .loading {
+ text-align: center;
+ width: 100%;
+ color: #888888;
+ }
+
+ .minfo {
+ color: $info-color;
+ }
+
+ .timehidden {
+ color: $fg2;
+ }
+
+ .undelay {
+ color: $undelay-color;
+ }
+
+ .verbose {
+ margin-bottom: 1em;
+
+ .no-realtime {
+ color: $cancelled-stop-color;
+ }
+ }
+
+ .messages {
+ i.material-icons {
+ font-size: 14px;
+ }
+ }
+
+ .details {
+ margin-top: 1em;
+ }
+
+ .mroute {
+ .important-stop {
+ color: $fg;
+ }
+
+ .generic-stop {
+ color: $fg2;
+ }
+
+ .additional-stop {
+ color: $additional-stop-color;
+ }
+
+ .cancelled-stop {
+ color: $cancelled-stop-color;
+ }
+
+ .past-stop {
+ list-style-type: disc;
+ }
+
+ .future-stop {
+ list-style-type: circle;
+ }
+
+ .time-early {
+ color: $early-stop-color;
+ }
+
+ .time-delayed {
+ color: $delayed-stop-color;
+ }
+
+ .time-sched-only {
+ color: $delayed-stop-color;
+ }
+
+ .time-sched-ontime {
+ color: $early-stop-color;
+ }
+
+
+ .annotation {
+ color: $fg2;
+ list-style-type: none;
+ padding-left: 3em;
+ }
+
+ .-sched:before {
+ content: " ";
+ }
+
+ .time-sched:after {
+ content: " ";
+ }
+
+ .time-sched-only:before {
+ content: "(";
+ }
+
+ .time-sched-only:after {
+ content: ")";
+ }
+
+ i.material-icons {
+ font-size: 14px;
+ }
+ }
+
+ .db-attr {
+ margin-bottom: 1em;
+
+ span {
+ margin-right: 0.5em;
+ }
+ }
+
+ }
+
+ .collapsed-moreinfo {
+ display: none;
+ }
+
+ .expanded-moreinfo {
+ display: block;
+ }
+}
+
+ul.ui-autocomplete {
+ max-height: 20em;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+div.geolocation {
+ text-align: center;
+}
+
+div.candidatestatus {
+ text-align: center;
+ color: #999999;
+}
+
+div.candidatelist a {
+ display: block;
+ text-decoration: none;
+ font-size: 1.4em;
+ padding-top: 0.3em;
+ text-align: center;
+ border-bottom: 1px solid #999999;
+}
+
+div.candidatelist a .distance:after {
+ content: " km";
+}
+
+div.candidatelist a .distance {
+ font-size: 0.6em;
+ color: #999999;
+ padding-top: 0.2em;
+ padding-bottom: 0.3em;
+}
+
+div.candidatelist a .traininfo {
+ font-size: 0.7em;
+ color: #999999;
+ padding-top: 0.2em;
+ padding-bottom: 0.3em;
+}
+
+div.config {
+ margin-top: 2em;
+ font-family: Sans-Serif;
+ color: $fg2;
+
+ a {
+ color: $link-color;
+ cursor: pointer;
+ text-decoration: none;
+ }
+}
+
+div.about {
+ margin-top: 1em;
+ font-family: Sans-Serif;
+ color: $fg2;
+
+ a {
+ color: $link-color;
+ text-decoration: none;
+ }
+}
+
+.notice {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid #bce8f1;
+ border-radius: 4px;
+ color: #31708f;
+ background-color: #d9edf7;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.warning {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid #faebcc;
+ border-radius: 4px;
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.error {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid #ebccd1;
+ border-radius: 4px;
+ color: #a94442;
+ background-color: #f2dede;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.error .errcode {
+ font-family: Monospace;
+ margin-top: 2em;
+ font-size: 100%;
+ color: #aaaaaa;
+}
+
+.container {
+ max-width: 60em;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+pre {
+ margin-bottom: 2em;
+}
+
+span.optional,
+span.notes {
+ color: $fg2;
+}
+
+.moresettings-header {
+ cursor: pointer;
+}
+
+.moresettings-header-collapsed:before {
+ content: "▹ "
+}
+
+.moresettings-header-expanded:before {
+ content: "▿ "
+}
+
+.moresettings-collapsed {
+ display: none;
+}
+
+.moresettings-expanded {
+ display: block;
+}
+
+.developers-header {
+ cursor: pointer;
+}
+
+.developers-header-collapsed:before {
+ content: "▹ "
+}
+
+.developers-header-expanded:before {
+ content: "▿ "
+}
+
+.developers-collapsed {
+ display: none;
+}
+
+.developers-expanded {
+ display: block;
+}
+
+div.break {
+ height: 1em;
+}
+
+div.field {
+ margin-top: 0.3em;
+ margin-bottom: 0.6em;
+}
+
+.disabledbutton {
+ display: inline-block;
+ vertical-align: baseline;
+ border-radius: 4px;
+ border: 1px solid #cccccc;
+ box-shadow: none;
+ padding: 0.9ex;
+ margin-right: 1em;
+}
+
+.smallbutton {
+ display: inline-block;
+ vertical-align: baseline;
+ border-radius: 4px;
+ border: 1px solid #2e6da4;
+ transition: background-color .3s;
+ color: #fff;
+ background-color: #337ab7;
+ cursor: pointer;
+ box-shadow: none;
+ padding: 0.9ex;
+ margin-right: 1em;
+}
+
+.smallbutton .material-icons,
+.disabledbutton .material-icons {
+ display: block;
+ float: left;
+ margin-right: 0.5ex;
+}
+
+.smallbutton img {
+ display: block;
+ float: left;
+ margin-right: 0.7ex;
+ height: 1.2em;
+}
+
+input, select, .button {
+ display: inline-block;
+ width: 60em;
+ max-width: 100%;
+ min-height: 1.8em;
+ border-radius: 4px;
+ color: $fg;
+ background-color: $bg;
+ border: 1px solid $bg1;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+ font-size: 90%;
+ text-align: center;
+ vertical-align: middle;
+}
+
+input[type="text"] {
+ width: 59em;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ text-align: left;
+ box-sizing: border-box;
+}
+
+select {
+ min-height: 2em;
+}
+
+input[type="checkbox"] {
+ width: 1.5em;
+ box-shadow: none;
+}
+
+input[type="submit"], .button {
+ transition: background-color .3s;
+ color: #fff;
+ background-color: #337ab7;
+ border-color: #2e6da4;
+ cursor: pointer;
+ box-shadow: none;
+ padding-top: 0.9ex;
+ padding-bottom: 0.9ex;
+}
+
+.button {
+ padding-top: 1.1ex;
+ padding-bottom: 0;
+}
+
+input[type="submit"]:active,
+input[type="submit"]:focus,
+input[type="submit"]:hover,
+.button:active,
+.button:focus,
+.button:hover,
+.smallbutton:active,
+.smallbutton:focus,
+.smallbutton:hover {
+ color: #fff;
+ background-color: #286090;
+ border-color: #204d74;
+}
+
+input[type="submit"]:active,
+.button:active {
+ box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
+}
+
+.button-active {
+ font-weight: bold;
+}
+
+.button-light {
+ color: $fg1;
+ background-color: $bg;
+ border-color: $bg1;
+}
+
+.button-light:active,
+.button-light:focus,
+.button-light:hover
+{
+ color: $fg1;
+ background-color: $button-hover;
+ border-color: $button-hover-border;
+}
+
+div.backendlink {
+ margin-top: 1ex;
+}
+
+div.notes {
+ margin-top: 2em;
+}
+
+div.notes ul {
+ margin-top: 1em;
+}
+div.app {
+ max-width: 60em;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.navbar-fixed {
+ position: relative;
+ z-index: 997;
+}
+
+.navbar-fixed nav {
+ position: fixed;
+}
+
+nav {
+ box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.12), 0 1px 5px 0 rgba(0,0,0,.2);
+}
+
+nav {
+ width: 100%;
+ overflow: hidden;
+}
+
+nav a {
+ color: #fff;
+}
+
+nav .nav-wrapper {
+ position: relative;
+ height: 100%;
+}
+
+nav i, nav i.material-icons {
+ display: block;
+ font-size: 24px;
+}
+
+nav .brand-logo {
+ position: absolute;
+ display: inline-block;
+ padding-left: 0.5rem;
+}
+
+nav ul {
+ margin: 0;
+ padding-left: 0;
+ list-style-type: none;
+}
+
+nav ul li {
+ transition: background-color .3s;
+ float: left;
+ padding: 0;
+ list-style-type: none;
+ background-color: #00838f;
+}
+
+nav ul a {
+ transition: background-color .3s;
+ font-size: 1rem;
+ color: #fff;
+ display: block;
+ padding: 0 15px;
+ cursor: pointer;
+}
+
+@media only screen and (max-width: 600px) {
+ div.app > ul > li {
+ font-size: 35%;
+ }
+ div.navbar-fixed {
+ height: 56px;
+ }
+ .moreinfo {
+ top: 56px;
+ }
+ nav {
+ height: 56px;
+ line-height: 56px;
+ }
+ nav .brand-logo {
+ font-size: 1.5rem;
+ }
+ nav .nav-wrapper i {
+ height: 56px;
+ line-height: 56px;
+ }
+}
+
+@media only screen and (min-width: 600px) {
+ div.app > ul > li {
+ font-size: 40%;
+ }
+ div.navbar-fixed {
+ height: 64px;
+ }
+ .moreinfo {
+ top: 64px;
+ }
+ nav {
+ height: 64px;
+ line-height: 64px;
+ }
+ nav .brand-logo {
+ font-size: 2.1rem;
+ }
+ nav .nav-wrapper i {
+ height: 64px;
+ line-height: 64px;
+ }
+}
+
+div.app .moreinfo {
+ font-size: 100%;
+}
diff --git a/sass/dark.scss b/sass/dark.scss
new file mode 100644
index 0000000..78b61b0
--- /dev/null
+++ b/sass/dark.scss
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011-2020 Birte Kristina Friesel
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+$bg: #101010;
+$fg: #ffffff;
+$link-color: #9999ff;
+$fg3: #999999;
+$fg2: #bbbbbb;
+$fg1: #dddddd;
+$bg05: #222222;
+$bg1: #444444;
+
+$li-border-color: $fg3;
+
+$replacement-color: #aaffaa;
+$replaced-color: #ffaaaa;
+
+$wagon-border-color: $fg3;
+$wagon-material-color: $fg2;
+
+$sbahn-color: #115511;
+$bahn-color: #333333;
+$fern-color: #551111;
+$ext-border-color: #993333;
+$tram-color: #441111;
+$ubahn-color: #071e62;
+$bus-color: #551155;
+
+$route-color: #dddddd;
+
+$info-color: #ff7777;
+$delay-color: #ff7777;
+$smalldelay-color: #dd9999;
+$undelay-color: #77ff77;
+$delaynorm-color: #dd9999;
+$undelaynorm-color: #99dd99;
+$ontime-color: #aaeeaa;
+
+$additional-stop-color: #77ff77;
+$cancelled-stop-color: #ff7777;
+
+$early-stop-color: #ccffcc;
+$delayed-stop-color: #ff9999;
+
+$cancelled-bg-color: #512f00;
+$past-bg-color: $bg05;
+
+$firstclass-wagon-color: #333300;
+$powercar-wagon-color: #222222;
+$closed-wagon-color: #222222;
+
+$button-hover: #111111;
+$button-hover-border: #333333;
+
+@import 'app.scss';
diff --git a/sass/light.scss b/sass/light.scss
new file mode 100644
index 0000000..60981b1
--- /dev/null
+++ b/sass/light.scss
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011-2020 Birte Kristina Friesel
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+$bg: #ffffff;
+$fg: #000000;
+$link-color: #000099;
+$fg3: #999999;
+$fg2: #666666;
+$fg1: #333333;
+$bg05: #dddddd;
+$bg1: #cccccc;
+
+$li-border-color: $fg3;
+
+$replacement-color: #006600;
+$replaced-color: #660000;
+
+$wagon-border-color: $fg3;
+$wagon-material-color: $fg2;
+
+$sbahn-color: #95d79f;
+$bahn-color: #eeeeee;
+$fern-color: #ffdddd;
+$ext-border-color: #ff6666;
+$tram-color: #ffcccc;
+$ubahn-color: #aac0ff;
+$bus-color: #eeaaee;
+
+$route-color: #444444;
+
+$info-color: #ff0000;
+$delay-color: #ff0000;
+$smalldelay-color: #bb3333;
+$undelay-color: #006600;
+$delaynorm-color: #bb3333;
+$undelaynorm-color: #338833;
+$ontime-color: #227722;
+
+$additional-stop-color: #009900;
+$cancelled-stop-color: #cc0000;
+
+$early-stop-color: #007700;
+$delayed-stop-color: #990000;
+
+$cancelled-bg-color: #ffe7d0;
+$past-bg-color: $bg05;
+
+$firstclass-wagon-color: #ffff99;
+$powercar-wagon-color: #cccccc;
+$closed-wagon-color: #dddddd;
+
+$button-hover: #e6e6e6;
+$button-hover-border: #adadad;
+
+@import 'app.scss';
diff --git a/scripts/asset-rebuild b/scripts/asset-rebuild
new file mode 100755
index 0000000..09597ad
--- /dev/null
+++ b/scripts/asset-rebuild
@@ -0,0 +1,14 @@
+#!/bin/sh
+# Copyright (C) 2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: CC0-1.0
+
+set -ex
+
+sassc -t compressed sass/dark.scss public/static/css/dark.min.css
+sassc -t compressed sass/light.scss public/static/css/light.min.css
+
+uglifyjs public/static/js/collapse.js -c -m > public/static/js/dbf.min.js
+uglifyjs public/static/js/geostop.js -c -m > public/static/js/geostop.min.js
+uglifyjs public/static/js/map-refresh.js -c -m > public/static/js/map-refresh.min.js
+uglifyjs public/static/js/marquee.js -c -m > public/static/js/marquee.min.js
diff --git a/scripts/asset-release b/scripts/asset-release
new file mode 100755
index 0000000..418477f
--- /dev/null
+++ b/scripts/asset-release
@@ -0,0 +1,18 @@
+#!/bin/sh
+# Copyright (C) 2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: CC0-1.0
+
+set -ex
+
+current="$(find public/static/v* | tail -n 1 | grep -o '...$')"
+prev=$((current - 1))
+next=$((current + 1))
+
+git mv public/static/v${prev} public/static/v${next}
+
+perl -pi -e "s!/v${current}/!/v${next}/!g" \
+ public/static/css/material-icons.css
+
+perl -pi -e "s!av = 'v${current}'!av = 'v${next}'!" \
+ templates/layouts/app.html.ep templates/layouts/legacy.html.ep
diff --git a/share/dbdb_wagen.json b/share/dbdb_wagen.json
new file mode 100644
index 0000000..4ff4be8
--- /dev/null
+++ b/share/dbdb_wagen.json
@@ -0,0 +1 @@
+{"58027":{"seats_comp":"18","name":"ICE 1 BR 401 2. Kl.-Mittelwagen (Bvmz 802.7)*","wc":"1","seats_open":"52","seats":70},"54062":{"name":"ICE 3 (M) BR 406 2. Kl.-Mittelwagen (Bvmz 406.2)","seats_comp":"17","wc":"2","seats":62,"seats_open":"45"},"54032.2":{"name":"ICE 3 BR 403, 2. Serie 2. Kl.-Mittelwagen (Bvmz 403.2)","seats_open":"74","seats":74,"wc":"2"},"DApza":{"name":"Wagen DApza 687.2","wc":"2","seats_open":"32","seats":32},"Bpmmdz":{"wc":"2","seats_open":"52","seats":52},"24125":{"wc":"2","seats":88,"seats_open":"88","name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 2412.5)"},"54068":{"name":"ICE 3 (M) BR 406 2. Kl.-Mittelwagen (Bpmbz 406.8)","seats_comp":"6","wc":"1","seats_open":"48","seats":54},"88122":{"name":"ICE 4 (7-tlg.) BR 412 Bordrestaurant/1. Klasse (ARmz 8812.2)","seats_open":"27","seats":27},"DBpbzfa":{"seats_comp":"8","name":"Wagen DBpbzfa 668.2","seats_open":"40","seats":48},"14120":{"wc":"2","seats":81,"seats_open":"67","name":"ICE 4 (13-tlg.) BR 412 1. Kl.-Mittelwagen (Apmz 1412.0)","seats_comp":"14"},"54038.2":{"name":"ICE 3 BR 403, 2. Serie 2. Kl.-Mittelwagen (Bpmbz 403.8)","seats_comp":"6","seats_open":"48","seats":54,"wc":"1"},"54063.r":{"name":"ICE 3 (M) BR 406 Redesign Bordrestaurant (WRmz 406.3)","seats":0},"Bpmmbdzf":{"seats":32,"seats_open":"32","name":"Wagen Bpmmbdzf 286"},"58014.r":{"wc":"1","seats_open":"37","seats":55,"seats_comp":"18"},"54036.1":{"name":"ICE 3 BR 403, 1. Serie 2. Kl.-Mittelwagen (Bpmz 403.6)","seats":74,"seats_open":"74","wc":"2"},"54073":{"seats":45,"seats_open":"37","name":"ICE 3 (MS) BR 407 2. Kl.-Servicewagen (Bpmbsz 407.3)","seats_comp":"8"},"34127":{"name":"ICE 4 (7-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 3412.7)","wc":"2","seats_open":"88","seats":88},"58010.r":{"seats_comp":"18","seats_open":"37","seats":55,"wc":"1"},"58040.r":{"seats":0,"name":"ICE 1 BR 401 modernisiert Bordrestaurant (WSmz 804.0)"},"41106_u":{},"48120":{"name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 4812.0)","wc":"2","seats":88,"seats_open":"88"},"41105_u":{},"58080":{"seats_open":"54","seats":54,"name":"ICE 2 BR 402 2. Kl.-Steuerwagen (Bpmzf 808.0)"},"Avmmz":{"seats_comp":"54","seats":54,"wc":"2"},"54035.1":{"name":"ICE 3 BR 403, 1. Serie 2. Kl.-Endwagen (Bpmzf 403.5)","seats":58,"seats_open":"58"},"54032.r":{"name":"ICE 3 BR 403 Redesign 2. Kl.-Mittelwagen (Bpmz 403.2)*","wc":"2","seats_open":"76","seats":76},"58029":{"name":"ICE 1 BR 401 2. Kl.-Mittelwagen (Bpmz 802.9)*","wc":"2","seats":74,"seats_open":"74"},"Apmmz":{"wc":"2","seats":54,"seats_open":"54","name":"Wagen Apmmz 118.5"},"54060.r":{"name":"ICE 3 (M) BR 406 Redesign 1. Kl.-Endwagen (Apmzf 406.0)","seats":41,"seats_open":"41"},"58026":{"seats_comp":"24","name":"ICE 1 BR 401 2. Kl.-Mittelwagen (Bvmz 802.6)","wc":"2","seats":71,"seats_open":"47"},"58120":{"seats_open":"59","seats":59,"wc":"2","name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Endwagen (Bpmdzf 5812.0)"},"54070":{"seats_open":"42","seats":42,"name":"ICE 3 (MS) BR 407 1. Kl.-Endwagen (Apmzf 407.0)"},"54110.2":{"name":"ICE T (7-tlg.) BR 411, 2. Serie 1. Kl.-Endwagen (Apmzf 411.0)","seats_comp":"8","seats_open":"37","seats":45},"58018":{"wc":"1","seats_open":"38","seats":56,"name":"ICE 1 BR 401 1. Kl.-Mittelwagen (Avmz 801.8)","seats_comp":"18"},"24127":{"name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 2412.7)","seats":88,"seats_open":"88","wc":"2"},"DApza_l":{},"54038.r":{"name":"ICE 3 BR 403 Redesign 2. Kl.-Mittelwagen (Bpmbz 403.8)","seats_comp":"6","seats_open":"45","seats":51},"58031":{"seats_open":"29","seats":35,"name":"Wagen Apmbsz 803.1","seats_comp":"6"},"Bpmz":{"wc":"2","seats_open":"80","seats":80,"name":"Wagen Bpmz 294"},"DBpbzfa_u":{},"41101_u":{},"54037.1":{"wc":"2","seats":74,"seats_open":"74","name":"ICE 3 BR 403, 1. Serie 2. Kl.-Mittelwagen (Bpmz 403.7)"},"54031.1":{"seats":48,"seats_open":"32","wc":"2","name":"ICE 3 BR 403, 1. Serie 1. Kl.-Mittelwagen (Avmz 403.1)","seats_comp":"16"},"58026.r":{"name":"ICE 1 BR 401 modernisiert 2. Kl. Mittelwagen (Bvmz 802.6)","seats_comp":"24","seats_open":"46","seats":70,"wc":"2"},"Bpmmz":{"wc":"2","seats":80,"seats_open":"80"},"54150":{"seats":43,"seats_open":"35","seats_comp":"8","name":"ICE T (5-tlg.) BR 415 1. Kl.-Endwagen (Apmzf 415.0)"},"54060":{"name":"ICE 3 (M) BR 406 1. Kl.-Endwagen (Apmzf 406.0)","seats":39,"seats_open":"39"},"54030.2":{"name":"ICE 3 BR 403, 2. Serie 1. Kl.-Endwagen (Apmzf 403.0)","seats":42,"seats_open":"42"},"41104_l":{},"08120":{"wc":"2","seats":64,"seats_open":"50","name":"ICE 4 (13-tlg.) BR 412 1. Kl.-Endwagen (Apmzf 0812.0)","seats_comp":"14"},"58018.r":{"name":"ICE 1 BR 401 modernisiert 1. Kl. Mittelwagen (Avmz 801.8)","seats_comp":"18","seats":55,"seats_open":"37","wc":"1"},"54033.r":{"seats":0,"name":"ICE 3 BR 403 Redesign Bordrestaurant (WRmz 403.3)"},"88120":{"seats":21,"seats_open":"21","name":"ICE 4 (13-tlg.) BR 412 Bordrestaurant/1. Klasse (ARmz 8812.0)"},"58066":{"name":"ICE 2 BR 402 2. Kl.-Mittelwagen (Bpmz 806.6)","seats_open":"78","seats":78,"wc":"2"},"58050":{"seats":53,"seats_open":"49","wc":"1","seats_comp":"4","name":"ICE 2 BR 402 1. Kl.-Mittelwagen (Apmz 805.0)"},"54111.1":{"seats_open":"12","seats":12,"wc":"2","name":"ICE T (7-tlg.) BR 411, 1. Serie 1. Kl.-/2. Kl.-Mittelwagen (ABpmz 411.1)"},"58031.r":{"seats_open":"38","seats":43,"seats_comp":"5","name":"ICE 1 BR 401 modernisiert 2. Kl. Servicewagen (Bpmbsz 803.1)"},"54117.1":{"seats":62,"seats_open":"62","wc":"2","name":"ICE T (7-tlg.) BR 411, 1. Serie 2. Kl.-Mittelwagen (Bpmz 411.7)"},"DBpza":{"seats":58,"seats_open":"58","wc":"2","name":"Wagen DBpza 682.2"},"Bpmmbdz":{"seats":57,"seats_open":"57","name":"Wagen Bpmmbdz 284"},"74122":{"seats_comp":"5","name":"ICE 4 (7-tlg.) BR 412 2. Kl.-Servicewagen (Bpmbsz 7412.2)","seats_open":"33","seats":38},"54030.r":{"seats_open":"43","seats":43,"name":"ICE 3 BR 403 Redesign 1. Kl.-Endwagen (Apmzf 403.0)"},"58053":{"wc":"2","seats":53,"seats_open":"49","name":"ICE 2 BR 402 1. Kl.-Mittelwagen (Apmz 805.3)","seats_comp":"4"},"54062.r":{"name":"ICE 3 (M) BR 406 Redesign 2. Kl.-Mittelwagen (Bpmz 406.2)","wc":"2","seats_open":"76","seats":76},"54072":{"name":"ICE 3 (MS) BR 407 Bordrestaurant /1. Klasse (ARmz 407.2)","seats":18,"seats_open":"18"},"Avmz":{"name":"Wagen Avmz 109","seats_comp":"54","wc":"2","seats":54},"54112.2":{"wc":"1","seats":6,"seats_comp":"6","name":"ICE T (7-tlg.) BR 411, 2. Serie Bordrestaurant (WRmz 411.2)"},"38122":{"name":"ICE 4 (7-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 3812.2)","wc":"2","seats_open":"88","seats":88},"DBpza_l":{},"54116.1":{"seats":62,"seats_open":"62","name":"ICE T (7-tlg.) BR 411, 1. Serie 2. Kl.-Mittelwagen (Bpmbz 411.6)"},"64120":{"seats":38,"seats_open":"33","name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Servicewagen (Bpmbsz 6412.0)","seats_comp":"5"},"58014":{"wc":"1","seats_open":"38","seats":56,"name":"ICE 1 BR 401 1. Kl.-Mittelwagen (Avmz 801.4)","seats_comp":"18"},"54063":{"seats":0,"name":"ICE 3 (M) BR 406 Bordbistro (BRmz 406.3)"},"54115.1":{"name":"ICE T (7-tlg.) BR 411, 1. Serie 2. Kl.-Endwagen (Bpmzf 411.5)","seats":55,"seats_open":"55"},"54068.r":{"seats_comp":"6","name":"ICE 3 (M) BR 406 Redesign 2. Kl.-Mittelwagen (Bpmbz 406.8)","seats_open":"45","seats":51},"54033.2":{"seats":0,"name":"ICE 3 BR 403, 2. Serie Bordbistro (BRmz 403.3)"},"54078":{"seats":76,"seats_open":"76","wc":"2","name":"ICE 3 (MS) BR 407 2. Kl.-Mittelwagen (Bpmz 407.8)"},"58040":{"seats":0,"name":"ICE 1 BR 401 Bordrestaurant (WSmz 804.0)"},"Bvmmz":{"seats_comp":"30","name":"Wagen Bvmmz 188.1","wc":"2","seats":70,"seats_open":"40"},"58010":{"wc":"1","seats":56,"seats_open":"38","seats_comp":"18","name":"ICE 1 BR 401 1. Kl.-Mittelwagen (Avmz 801.0)"},"41104":{"name":"Intercity 2 – Stadler KISS BR 4110 2. Kl.-Mittelwagen (DBpbza 110.D)","seats_open":"51","seats":51,"wc":"3"},"54118.2":{"seats":68,"seats_open":"68","name":"Wagen Bpmdz 411.8"},"Bvmmsz":{"seats_comp":"22","wc":"2","seats_open":"40","seats":62},"ARkimbz":{"seats_comp":"8","seats_open":"10","seats":18},"54061.r":{"wc":"2","seats":50,"seats_open":"33","name":"ICE 3 (M) BR 406 Redesign 1. Kl.-Mittelwagen (Avmz 406.1)","seats_comp":"17"},"Bvmsz":{"seats_comp":"22","seats_open":"40","seats":62,"wc":"2"},"54067.r":{"seats":76,"seats_open":"76","wc":"2","name":"ICE 3 (M) BR 406 Redesign 2. Kl.-Mittelwagen (Bpmz 406.7)"},"18120":{"seats_comp":"14","name":"ICE 4 (13-tlg.) BR 412 1. Kl.-Mittelwagen (Apmz 1812.0)","seats_open":"67","seats":81,"wc":"2"},"54117.2":{"name":"ICE T (7-tlg.) BR 411, 2. Serie 2. Kl.-Mittelwagen (Bpmz 411.7)","seats":68,"seats_open":"68","wc":"2"},"24120":{"name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 2412.0)","wc":"2","seats":88,"seats_open":"88"},"54111.2":{"wc":"2","seats_open":"12","seats":12,"name":"ICE T (7-tlg.) BR 411, 2. Serie 1. Kl.-/2. Kl.-Mittelwagen (ABpmz 411.1)"},"54077":{"seats_open":"76","seats":76,"wc":"2","name":"ICE 3 (MS) BR 407 2. Kl.-Mittelwagen (Bpmz 407.7)"},"54071":{"wc":"2","seats_open":"39","seats":39,"name":"ICE 3 (MS) BR 407 1. Kl.-Mittelwagen (Apmz 407.1)"},"58028.r":{"wc":"2","seats_open":"46","seats":70,"name":"ICE 1 BR 401 modernisiert 2. Kl. Mittelwagen (Bvmz 802.8)","seats_comp":"24"},"41104_u":{},"Bimmdzf":{"seats_comp":"10","name":"Wagen Bimmdzf 287","seats_open":"30","seats":40},"58023":{"seats":71,"seats_open":"47","wc":"2","seats_comp":"24","name":"ICE 1 BR 401 2. Kl.-Mittelwagen (Bvmz 802.3)"},"54030.1":{"seats":42,"seats_open":"42","name":"ICE 3 BR 403, 1. Serie 1. Kl.-Endwagen (Apmzf 403.0)"},"98120":{"seats":88,"seats_open":"88","wc":"2","name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 9812.0)"},"54118.1":{"seats":64,"seats_open":"64","name":"Wagen Bpmdz 411.8"},"54065.r":{"name":"ICE 3 (M) BR 406 Redesign 2. Kl.-Endwagen (Bpmzf 406.5)","seats_open":"56","seats":56},"68122":{"name":"ICE 4 (7-tlg.) BR 412 1. Kl.-Endwagen (Apmzf 6812.2)","seats_comp":"14","wc":"2","seats":64,"seats_open":"50"},"ARkimmbz":{"seats_comp":"8","seats":18,"seats_open":"10"},"58020":{"seats_open":"47","seats":71,"wc":"2","seats_comp":"24","name":"ICE 1 BR 401 2. Kl.-Mittelwagen (Bvmz 802.0)"},"54066.r":{"name":"ICE 3 (M) BR 406 Redesign 2. Kl.-Mittelwagen (Bpmz 406.6)","wc":"2","seats":76,"seats_open":"76"},"54076":{"seats":72,"seats_open":"72","wc":"2","name":"ICE 3 (MS) BR 407 2. Kl.-Mittelwagen (Bpmz 407.6)"},"54033.1":{"seats":0,"name":"ICE 3 BR 403, 1. Serie Bordbistro (BRmz 403.3)"},"DBpbzfa_l":{},"54115.2":{"seats_open":"58","seats":58,"name":"ICE T (7-tlg.) BR 411, 2. Serie 2. Kl.-Endwagen (Bpmzf 411.5)"},"54116.2":{"name":"ICE T (7-tlg.) BR 411, 2. Serie 2. Kl.-Mittelwagen (Bpmbz 411.6)","seats_open":"66","seats":66},"54075":{"seats":64,"seats_open":"64","name":"ICE 3 (MS) BR 407 2. Kl.-Endwagen (Bpmzf 407.5)"},"DBpza_u":{},"Bpmmbz":{"seats_open":"69","seats":69},"54112.1":{"name":"ICE T (7-tlg.) BR 411, 1. Serie Bordrestaurant (WRmz 411.2)","seats_comp":"6","wc":"1","seats":6},"58070":{"name":"ICE 2 BR 402 Bordrestaurant (WRmbsz 807.0)","seats":0},"24123":{"wc":"2","seats":88,"seats_open":"88","name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 2412.3)"},"41105_l":{},"54035.2":{"seats_open":"58","seats":58,"name":"ICE 3 BR 403, 2. Serie 2. Kl.-Endwagen (Bpmzf 403.5)"},"54156":{"name":"ICE T (5-tlg.) BR 415 2. Kl.-Mittelwagen (Bpmbz 415.6)","seats_open":"62","seats":62},"54066":{"wc":"2","seats":72,"seats_open":"72","name":"ICE 3 (M) BR 406 2. Kl.-Mittelwagen (Bpmz 406.6)"},"54155":{"name":"ICE T (5-tlg.) BR 415 2. Kl.-Endwagen (Bpmzf 415.5)","seats":55,"seats_open":"55"},"54065":{"name":"ICE 3 (M) BR 406 2. Kl.-Endwagen (Bpmzf 406.5)","seats_open":"54","seats":54},"85023.r":{"name":"ICE 1 BR 401 modernisiert 2. Kl. Mittelwagen (Bvmz 802.3)","seats_comp":"24","wc":"2","seats_open":"46","seats":70},"54031.r":{"wc":"2","seats":50,"seats_open":"33","seats_comp":"17","name":"ICE 3 BR 403 Redesign 1. Kl.-Mittelwagen (Avmz 403.1)"},"54037.r":{"seats_open":"76","seats":76,"wc":"2","name":"ICE 3 BR 403 Redesign 2. Kl.-Mittelwagen (Bpmz 403.7)"},"54036.2":{"wc":"2","seats":74,"seats_open":"74","name":"ICE 3 BR 403, 2. Serie 2. Kl.-Mittelwagen (Bpmz 403.6)"},"41106_l":{},"78122":{"name":"ICE 4 (7-tlg.) BR 412 2. Kl.-Endwagen (Bpmdzf 7812.2)","seats_open":"59","seats":59,"wc":"2"},"54038.1":{"wc":"1","seats":54,"seats_open":"48","name":"ICE 3 BR 403, 1. Serie 2. Kl.-Mittelwagen (Bpmbz 403.8)","seats_comp":"6"},"58060":{"wc":"2","seats":65,"seats_open":"57","name":"ICE 2 BR 402 2. Kl.-Mittelwagen (Bpmbz 806.0)","seats_comp":"8"},"41101":{"name":"Intercity 2 – Stadler KISS BR 4110 2. Kl.-Endwagen (DBpdzfa 110.A)","seats":43,"seats_open":"43"},"24128":{"seats_open":"88","seats":88,"wc":"2","name":"ICE 4 (13-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 2412.8)"},"54032.1":{"seats_open":"44","seats":61,"wc":"2","seats_comp":"17","name":"ICE 3 BR 403, 1. Serie 2. Kl.-Mittelwagen (Bvmz 403.2)"},"Bpmbz":{"seats_open":"75","seats":75,"name":"Wagen Bpmbz 294"},"54031.2":{"seats_open":"32","seats":48,"wc":"2","seats_comp":"16","name":"ICE 3 BR 403, 2. Serie 1. Kl.-Mittelwagen (Avmz 403.1)"},"41101_l":{},"54037.2":{"name":"ICE 3 BR 403, 2. Serie 2. Kl.-Mittelwagen (Bpmz 403.7)","wc":"2","seats":74,"seats_open":"74"},"58063":{"wc":"2","seats":78,"seats_open":"78","name":"ICE 2 BR 402 2. Kl.-Mittelwagen (Bpmz 806.3)"},"54036.r":{"wc":"2","seats_open":"76","seats":76,"name":"ICE 3 BR 403 Redesign 2. Kl.-Mittelwagen (Bpmz 403.6)"},"54061":{"seats":46,"seats_open":"30","wc":"2","seats_comp":"16","name":"ICE 3 (M) BR 406 1. Kl.-Mittelwagen (Avmz 406.1)"},"54151":{"seats_comp":"6","name":"ICE T (5-tlg.) BR 415 Bordbistro/2. Klasse (BRpmz 415.1)","wc":"1","seats":22,"seats_open":"16"},"54035.r":{"seats_open":"60","seats":60,"name":"ICE 3 BR 403 Redesign 2. Kl.-Endwagen (Bpmzf 403.5)"},"54067":{"wc":"2","seats_open":"74","seats":74,"name":"ICE 3 (M) BR 406 2. Kl.-Mittelwagen (Bpmz 406.7)"},"54157":{"name":"ICE T (5-tlg.) BR 415 2. Kl.-Mittelwagen (Bpmz 415.7)","wc":"2","seats":62,"seats_open":"62"},"DApza_u":{},"41106":{"name":"Intercity 2 – Stadler KISS BR 4110 1./2. Kl.-Endwagen (DABpzfa 110.F)","seats_open":"31","seats":31},"58023.r":{"wc":"2","seats":70,"seats_open":"46","name":"ICE 1 BR 401 modernisiert 2. Kl. Mittelwagen (Bvmz 802.3)","seats_comp":"24"},"54110.1":{"seats_open":"37","seats":45,"name":"ICE T (7-tlg.) BR 411, 1. Serie 1. Kl.-Endwagen (Apmzf 411.0)","seats_comp":"8"},"58028":{"seats_comp":"24","name":"ICE 1 BR 401 2. Kl.-Mittelwagen (Bvmz 802.8)","wc":"2","seats":71,"seats_open":"47"},"41105":{"name":"Intercity 2 – Stadler KISS BR 4110 2. Kl.-Mittelwagen (DBpza 110.E)","wc":"2","seats":51,"seats_open":"51"},"34122":{"name":"ICE 4 (7-tlg.) BR 412 2. Kl.-Mittelwagen (Bpmz 3412.2)","seats_open":"88","seats":88,"wc":"2"}} \ No newline at end of file
diff --git a/t/01-basic.t b/t/01-basic.t
index da7b654..f55633a 100644
--- a/t/01-basic.t
+++ b/t/01-basic.t
@@ -1,10 +1,15 @@
+#!/usr/bin/env perl
+# Copyright (C) 2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: CC0-1.0
+
use Test::More;
use Test::Mojo;
use FindBin;
require "$FindBin::Bin/../index.pl";
-my $t = Test::Mojo->new;
-$t->get_ok('/')->status_is(200)->content_like(qr/db-infoscreen/);
+my $t = Test::Mojo->new('DBInfoscreen');
+$t->get_ok('/')->status_is(200)->content_like(qr/DBF/);
done_testing();
diff --git a/t/22-json.t b/t/22-json.t
new file mode 100644
index 0000000..c133751
--- /dev/null
+++ b/t/22-json.t
@@ -0,0 +1,46 @@
+#!/usr/bin/env perl
+# Copyright (C) 2020 Birte Kristina Friesel
+#
+# SPDX-License-Identifier: CC0-1.0
+
+use strict;
+use warnings;
+use 5.014;
+use Test::More;
+use Test::Mojo;
+
+use FindBin;
+require "$FindBin::Bin/../index.pl";
+
+my $t = Test::Mojo->new('DBInfoscreen');
+
+# Note: These tests depends on IRIS live data. If it fails, it -might- also
+# be because of IRIS problems or unanticipated schedule changes.
+# TODO: Support mock XML from hard disk.
+
+$t->get_ok('/EDUV?mode=json&version=1')->status_is(200)
+ ->json_has( '/departures', 'has departures' )
+ ->json_has( '/departures/0', 'has a departure' )
+ ->json_has( '/departures/0/route', '.route' )
+ ->json_has( '/departures/0/delay', '.delay' )
+ ->json_like( '/departures/0/destination',
+ qr{ ^ (Dortmund|Bochum|Essen|D.sseldorf|Solingen) \s Hbf $}x,
+ '.destination' )
+ ->json_like( '/departures/0/isCancelled', qr{ ^ 0 | 1 $ }x, '.is_cancelled' )
+ ->json_has( '/departures/0/messages', '.messages' )
+ ->json_has( '/departures/0/messages/delay', '.messages.delay' )
+ ->json_has( '/departures/0/messages/qos', '.messages.qos' )
+ ->json_like( '/departures/0/time', qr{ ^ \d \d? : \d\d $ }x, '.time' )
+ ->json_is( '/departures/0/train', 'S 1', '.train' )
+ ->json_like( '/departures/0/platform', qr{ ^ 1 | 2 $}x, '.platform' )
+ ->json_like( '/departures/0/route/0/name',
+ qr{ ^ (Dortmund|Bochum|Essen|D.sseldorf|Solingen) \s Hbf $}x, '.route[0]' )
+ ->json_like( '/departures/0/via/0',
+ qr{ ^ Dortmund-Dorstfeld \s S.d | Dortmund-Oespel $}x, '.via[0]' );
+
+$t->get_ok('/EDUV?mode=json&version=1&callback=my_callback')->status_is(200)
+ ->content_like( qr{ ^ my_callback \( }x, 'json callback works' );
+
+# ) <- just here to fix bracket grouping in vim
+
+done_testing();
diff --git a/t/22-marudor.t b/t/22-marudor.t
deleted file mode 100644
index e11bb88..0000000
--- a/t/22-marudor.t
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use 5.014;
-use Test::More;
-use Test::Mojo;
-
-use FindBin;
-require "$FindBin::Bin/../index.pl";
-
-my $t = Test::Mojo->new;
-
-# Note: These tests depends on IRIS live data. If it fails, it -might- also
-# be because of IRIS problems or unanticipated schedule changes.
-# TODO: Support mock XML from hard disk.
-
-$t->get_ok('/EDUV?mode=marudor_v1&backend=iris')
- ->status_is(200)
- ->json_has('/api_version', 'has api_version')
- ->json_has('/version', 'has version')
- ->json_has('/preformatted', 'has preformatted')
- ->json_has('/preformatted/0', 'has a departure')
- ->json_has('/preformatted/0/additional_stops', '.additional_stops')
- ->json_has('/preformatted/0/canceled_stops', '.canceled_stops')
- ->json_has('/preformatted/0/delay', '.delay')
- ->json_like('/preformatted/0/destination',
- qr{ ^ (Dortmund|Bochum|Essen|D.sseldorf|Solingen) \s Hbf $}x,
- '.destination')
- ->json_has('/preformatted/0/info', '.info')
- ->json_like('/preformatted/0/is_cancelled', qr{ ^ 0 | 1 $ }x, '.is_cancelled')
- ->json_has('/preformatted/0/messages', '.messages')
- ->json_has('/preformatted/0/messages/delay', '.messages.delay')
- ->json_has('/preformatted/0/messages/qos', '.messages.qos')
- ->json_like('/preformatted/0/time', qr{ ^ \d \d? : \d\d $ }x, '.time')
- ->json_is('/preformatted/0/train', 'S 1', '.train')
- ->json_like('/preformatted/0/platform', qr{ ^ 1 | 2 $}x, '.platform')
- ->json_like('/preformatted/0/scheduled_route/0',
- qr{ ^ (Dortmund|Bochum|Essen|D.sseldorf|Solingen) \s Hbf $}x,
- '.scheduled_route[0]')
- ->json_like('/preformatted/0/via/0',
- qr{ ^ Dortmund-Dorstfeld \s S.d | Dortmund-Oespel $}x,
- '.scheduled_route[0]')
- ;
-
-$t->get_ok('/EDUV?mode=marudor_v1&backend=iris&callback=my_callback')
- ->status_is(200)
- ->content_like(qr{ ^ my_callback \( }x, 'json callback works');
-# ) <- just here to fix bracket grouping in vim
-
-done_testing();
diff --git a/t/31-clean-ris.t b/t/31-clean-ris.t
deleted file mode 100644
index 424bba7..0000000
--- a/t/31-clean-ris.t
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use 5.014;
-use Test::More;
-use Test::Mojo;
-
-use FindBin;
-require "$FindBin::Bin/../index.pl";
-
-my $t = Test::Mojo->new;
-
-# Note: These tests depends on RIS live data. If it fails, it -might- also
-# be because of RIS problems or unanticipated schedule changes.
-# TODO: Support mock XML from hard disk.
-
-$t->get_ok('/Dortmund Universitat?backend=ris')
- ->status_is(200)
- ->content_like(qr{S 1}, 'train name')
- ->content_like(qr{Dortmund Hbf}, 'dest')
- ->content_like(qr{Dortmund-Oespel}, 'via')
- ;
-
-done_testing();
diff --git a/templates/_error.html.ep b/templates/_error.html.ep
new file mode 100644
index 0000000..8585f90
--- /dev/null
+++ b/templates/_error.html.ep
@@ -0,0 +1,5 @@
+<div class="error"><strong>Fehler:</strong>
+<p style="font-family: Monospace;">
+%= $error
+</p>
+</div>
diff --git a/templates/_map_infobox.html.ep b/templates/_map_infobox.html.ep
new file mode 100644
index 0000000..16625f5
--- /dev/null
+++ b/templates/_map_infobox.html.ep
@@ -0,0 +1,73 @@
+<div class="container" id="infobox" style="margin-top: 1ex; margin-bottom: 1ex;">
+<div class="journey" id="jdata"
+data-req="<%= stash('ajax_req') =~ s{#}{%23}gr %>"
+data-route="<%= stash('ajax_route') %>"
+data-poly="<%= stash('ajax_polyline') %>"
+>
+ Fahrt
+ % if (stash('train_no')) {
+ <strong><%= stash('train_no') %></strong>
+ % }
+ von <strong><%= stash('origin')->{name} %></strong>
+ nach <strong><%= stash('destination')->{name} %></strong>
+</div>
+% if (my $next = stash('next_stop')) {
+ <div class="nextstop">
+ % if ($next->{type} eq 'present' and $next->{station}{dep} and $next->{station}{arr}) {
+ Aufenthalt in <strong><%= $next->{station}{name} %></strong>
+ % if ($next->{station}{platform}) {
+ an Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ bis <strong><%= $next->{station}{dep}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{dep_delay}) {
+ %= sprintf('(%+d)', $next->{station}{dep_delay})
+ % }
+ % }
+ % elsif ($next->{type} eq 'present' and $next->{station}{dep}) {
+ Abfahrt in <strong><%= $next->{station}{name} %></strong>
+ % if ($next->{station}{platform}) {
+ von Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ um <strong><%= $next->{station}{dep}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{dep_delay}) {
+ %= sprintf('(%+d)', $next->{station}{dep_delay})
+ % }
+ % }
+ % elsif ($next->{type} eq 'present' and $next->{station}{arr}) {
+ Endstation erreicht um
+ <strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{platform}) {
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ % if ($next->{station}{arr_delay}) {
+ %= sprintf('(%+d)', $next->{station}{arr_delay})
+ % }
+ % }
+ % elsif ($next->{type} eq 'present' and $next->{station}{arr}) {
+ Zug steht in
+ <strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{platform}) {
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ % }
+ % elsif ($next->{type} eq 'next' and $next->{station}{arr}) {
+ Nächster Halt:
+ <strong><%= $next->{station}{name} %></strong>
+ um <strong><%= $next->{station}{arr}->strftime('%H:%M') %></strong>
+ % if ($next->{station}{arr_delay}) {
+ %= sprintf('(%+d)', $next->{station}{arr_delay})
+ % }
+ % if ($next->{station}{platform}) {
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ % }
+ % elsif ($next->{type} eq 'next') {
+ Nächster Halt:
+ <strong><%= $next->{station}{name} %></strong>
+ % if ($next->{station}{platform}) {
+ auf Gleis <strong><%= $next->{station}{platform} %></strong>
+ % }
+ % }
+ </div>
+% }
+</div>
diff --git a/templates/_train_attr.html.ep b/templates/_train_attr.html.ep
new file mode 100644
index 0000000..1b40d12
--- /dev/null
+++ b/templates/_train_attr.html.ep
@@ -0,0 +1,18 @@
+% if ($attr->{series}) {
+ <span>BR <%= $attr->{series} %></span>
+% }
+% if ($attr->{vmax}) {
+ <span><%= $attr->{vmax} %> km/h</span>
+% }
+% if ($attr->{length}) {
+ <span>⇤ <%= $attr->{length} %>m ⇥</span>
+% }
+% if ($attr->{weight}) {
+ <span><%= $attr->{weight} %>t</span>
+% }
+% if ($attr->{brakingPercentage}) {
+ <span>λ=<%= $attr->{brakingPercentage} %></span>
+% }
+% if ($with_station and $attr->{range}[0]) {
+ <span>ab <%= $attr->{range}[0] %></span>
+% }
diff --git a/templates/_train_details.html.ep b/templates/_train_details.html.ep
new file mode 100644
index 0000000..2c18da2
--- /dev/null
+++ b/templates/_train_details.html.ep
@@ -0,0 +1,415 @@
+ <div class="mheader">
+ <div>
+% if ($departure->{train_no} or $departure->{train_line}) {
+ <span class="train-line <%= $linetype %>"><%= $departure->{train_type} %>
+ %= $departure->{train_line} // $departure->{train_no}
+ </span>
+ <span class="train-no"><%= $departure->{train_line} ? $departure->{train_no} : q{} %></span>
+% }
+ </div>
+ <div>
+% if ($departure->{origin}) {
+ <span class="train-origin"><%= $departure->{origin} %></span>
+ →
+% }
+ <span class="train-dest"><%= $departure->{destination} // q{???} %></span>
+ </div>
+ </div> <!-- mheader -->
+ <div class="mfooter">
+ <div class="dataline">
+ <div>
+ <div class="arrival <%= $departure->{arrival_hidden} ? 'timehidden' : q{} %>">
+% if ($departure->{is_cancelled} and $departure->{sched_arrival}) {
+ <span class="minfo">An: ––:––</span><br/>Plan: <%= $departure->{sched_arrival} %>
+% }
+% elsif ($departure->{arrival_is_cancelled}) {
+ <span class="minfo">Beginnt hier</span><br/>Plan: <%= $departure->{sched_arrival} %>
+% }
+% elsif ($departure->{arrival}) {
+% if ($departure->{arrival} ne $departure->{sched_arrival}) {
+ % if (($departure->{arrival_delay} // 0) < 0) {
+ An: <span class="undelay"><%= $departure->{arrival} %></span>
+ % }
+ % else {
+ An: <span class="minfo"><%= $departure->{arrival} %></span>
+ % }
+ <br/>Plan: <%= $departure->{sched_arrival} %>
+% }
+% else {
+ An: <%= $departure->{arrival} %>
+% }
+% }
+% elsif ($departure->{sched_arrival}) {
+ An: <%= $departure->{sched_arrival} %>
+% }
+% elsif ($departure->{prep_time}) {
+ Ein: <%= $departure->{prep_time} %>
+% }
+% if ($departure->{tz_offset} and $departure->{local_sched_arr}) {
+ <br/>Lokal: <%= $departure->{local_sched_arr}->strftime('%H:%M') %>
+% }
+ </div>
+ </div>
+ <div>
+ <div class="platform">
+% if (@{$departure->{replaced_by}}) {
+% for my $replacement (@{$departure->{replaced_by}}) {
+ <span class="replaced">Ersatzfahrt: <a href="/z/<%= $replacement %>/<%= stash('station_name') // q{} %>"><%= $replacement %></a></span><br/>
+% }
+% }
+% if (@{$departure->{replacement_for}}) {
+% for my $replacement (@{$departure->{replacement_for}}) {
+ <span class="replacement">Ersatzfahrt für <a href="/z/<%= $replacement %>/<%= stash('station_name') // q{} %>"><%= $replacement %></a></span><br/>
+% }
+% }
+% if ($departure->{arrival_is_cancelled} and $departure->{departure_is_cancelled}) {
+ <span class="minfo">Fahrt fällt aus</span>
+% }
+% else {
+% my $left = '';
+% my $right = '';
+% if ($departure->{wr_direction} and $departure->{wr_direction} =~ m{l}) {
+% $left = '◀ ';
+% }
+% elsif ($departure->{wr_direction} and $departure->{wr_direction} =~ m{r}) {
+% $right = ' ▶';
+% }
+% if ($departure->{scheduled_platform} and $departure->{platform}
+% and $departure->{scheduled_platform} ne $departure->{platform}) {
+ <span class="minfo"><%= $left %>Gleis <%= $departure->{platform} %><%= $right %></span>
+% }
+% elsif ($departure->{scheduled_platform} or $departure->{platform}) {
+ <%= $left %>Gleis <%= $departure->{platform} // $departure->{scheduled_platform} %><%= $right %>
+% }
+% }
+% if ($departure->{arrival_hidden} and not $departure->{prep_time}) {
+ <br/><span class="timehidden">Nur Einstieg</span>
+% }
+% if ($departure->{departure_hidden}) {
+ <br/><span class="timehidden">Nur Ausstieg</span>
+% }
+ </div>
+ </div>
+ <div>
+ <div class="departure <%= $departure->{departure_hidden} ? 'timehidden' : q{} %>">
+% if ($departure->{is_cancelled} and $departure->{sched_departure}) {
+ <span class="minfo">Ab: ––:––</span><br/>Plan: <%= $departure->{sched_departure} %>
+% }
+% elsif ($departure->{departure_is_cancelled}) {
+ <span class="minfo">Endet hier</span><br/>Plan: <%= $departure->{sched_departure} %>
+% }
+% elsif ($departure->{departure}) {
+% if ($departure->{departure} ne $departure->{sched_departure}) {
+ Ab: <span class="minfo"><%= $departure->{departure} %></span>
+ <br/>Plan: <%= $departure->{sched_departure} %>
+% }
+% else {
+ Ab: <%= $departure->{departure} %>
+% }
+% }
+% elsif ($departure->{sched_departure}) {
+ Ab: <%= $departure->{sched_departure} %>
+% }
+% if ($departure->{tz_offset} and $departure->{local_sched_dep}) {
+ <br/>Lokal: <%= $departure->{local_sched_dep}->strftime('%H:%M') %>
+% }
+ </div>
+ </div>
+ </div> <!-- dataline -->
+% if (my $wr = $departure->{wr}) {
+ <div class="wagonorder-preview">
+% my $left = defined $wr->direction ? $wr->direction == 100 ? q{} : '←' : q{};
+% my $right = defined $wr->direction ? $wr->direction == 100 ? '→' : q{} : q{};
+% if ($departure->{wr_direction} and $departure->{wr_direction} =~ m{l}) {
+% $left = '◀';
+% $right = q{};
+% }
+% elsif ($departure->{wr_direction} and $departure->{wr_direction} =~ m{r}) {
+% $left = q{};
+% $right = '▶';
+% }
+ <a href="/carriage-formation?<%= $departure->{wr_link} %>&amp;e=<%= $departure->{wr_direction} // '' %>">
+ %= $left
+ % for my $entry ((defined $departure->{wr_direction_num} and $departure->{wr_direction_num} != $wr->direction) ? reverse @{$departure->{wr_preview} // []} : @{$departure->{wr_preview} // []}) {
+ % if ($entry->[1]) {
+ <span class="<%= $entry->[1] %>"><%= $entry->[0] %></span>
+ % }
+ % else {
+ %= $entry->[0]
+ % }
+ % }
+ %= $right
+ </a>
+ </div>
+% }
+ <div class="verbose">
+% if ($departure->{trip_id}) {
+% if (stash('station_name')) {
+ <a class="smallbutton" href="/map/<%= $departure->{trip_id} =~ s{#}{%23}gr %>/<%= $departure->{train_line} || 0 %>?from=<%= stash('station_name') %>&amp;dbris=<%= param('dbris') %>&amp;efa=<%= param('efa') // q{} %>&amp;hafas=<%= param('hafas') // q{} %>"><i class="material-icons" aria-hidden="true">map</i> Karte</a>
+% }
+% else {
+ <a class="smallbutton" href="/map/<%= $departure->{trip_id} =~ s{#}{%23}gr %>/<%= $departure->{train_line} || 0 %>?dbris=<%= param('dbris') %>&amp;efa=<%= param('efa') // q{} %>&amp;hafas=<%= param('hafas') // q{} %>"><i class="material-icons" aria-hidden="true">map</i> Karte</a>
+% }
+% }
+% if ($departure->{wr_link}) {
+ <a class="smallbutton" href="/carriage-formation?<%= $departure->{wr_link} %>&amp;e=<%= $departure->{wr_direction} // '' %>"><i class="material-icons" aria-hidden="true">train</i> <%= $departure->{wr_text} || 'Wagen' %>
+ </a>
+% }
+% if ($departure->{trip_id} and param('dbris') and param('dbris') eq 'bahn.de') {
+ <a class="smallbutton" href="https://bahn.expert/details/x/h/<%= Mojo::Util::url_escape( $departure->{trip_id} ) %>"><img src="/static/icons/bahn-expert.svg">Details</a>
+% } elsif ($departure->{train_type} and $departure->{train_no} and (not param('hafas') or param('hafas') eq 'DB')) {
+ <a class="smallbutton" href="https://bahn.expert/details/<%= $departure->{train_type} %>%20<%= $departure->{train_no} %>/<%= ($departure->{date} // DateTime->now(time_zone => 'Europe/Berlin'))->iso8601 %>?evaNumberAlongRoute=<%= $departure->{eva} %>"><img src="/static/icons/bahn-expert.svg">Details</a>
+% }
+% for my $link (@{$departure->{links}}) {
+ <a class="smallbutton" href="<%= $link->[1] %>"><i class="material-icons" aria-hidden="true">warning</i> <%= $link->[0] %></a>
+% }
+ </div>
+
+% if (not $departure->{departure_is_cancelled}) {
+% if (my $u = $departure->{utilization}) {
+ <div class="verbose">
+% my ($text, $icon1, $icon2) = utilization_icon($u);
+ <%= $text %><span style="padding-right: 0.5em;">.</span> 1. <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;"><%= $icon1 %></i> 2. <i class="material-icons" aria-hidden="true" style="vertical-align: bottom;"><%= $icon2 %></i>
+ </div>
+% }
+% elsif (my $o = $departure->{occupancy}) {
+ <div class="verbose">
+% my ($text, $icon) = occupancy_icon($o);
+ <%= $text %><span style="padding-right: 0.5em;">.</span> <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;"><%= $icon %></i></i>
+ </div>
+% }
+% }
+%
+% if ($departure->{missing_realtime}) {
+ <div class="verbose">
+ <i class="material-icons no-realtime" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;">gps_off</i> Echtzeitdaten fehlen. Ob die Zugfahrt wie im Fahrplan vorgesehen stattfindet, ist nicht bekannt.
+ </div>
+% }
+% elsif ($departure->{no_realtime_yet}) {
+ <div class="verbose">
+ <i class="material-icons" aria-hidden="true" style="padding-right: 0.5em; vertical-align: bottom;">gps_off</i> Für diese Zugfahrt sind derzeit nur Fahrplandaten bekannt.
+ </div>
+% }
+
+% if ($departure->{moreinfo} and @{$departure->{moreinfo}}) {
+ Meldungen
+ <ul class="messages">
+% for my $pair (@{$departure->{moreinfo}}) {
+ <li>
+% if (ref($pair->[0]) eq 'DateTime') {
+% if ($pair->[0]->day != $dt_now->day) {
+% $pair->[0]->set_locale('de_DE');
+%= $pair->[0]->strftime('%a %H:%M')
+% }
+% else {
+%= $pair->[0]->strftime('%H:%M')
+% }
+ <span class="reason">
+%= $pair->[1]
+ </span>
+% }
+% else {
+% if ($pair->[1]{icon}) {
+ <i class="material-icons"><%= $pair->[1]{icon} %></i>
+% }
+%= $pair->[0]
+% if (length($pair->[0]) > 25) {
+ <br/>
+% }
+ <span class="reason">
+%= $pair->[1]{text}
+ </span>
+% }
+ </li>
+% }
+% if ($departure->{route_info}) {
+ <li><%= $departure->{route_info} %></li>
+% }
+ </ul>
+% }
+% if ($departure->{route_pre_diff} and $departure->{route_post_diff}) {
+% if ($departure->{date}) {
+ Fahrtverlauf am
+% if (stash('train') !~ m{[|]}) {
+ <a href="<%= url_for('train', train => stash('train'))->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas'), date => $departure->{date}->clone->subtract(days => 1)->strftime('%d.%m.%Y'), highlight => param('highlight') // stash('station')}) %>">◀</a>
+% }
+%= $departure->{date}->strftime('%d.%m.%Y')
+% if (stash('train') !~ m{[|]}) {
+ <a href="<%= url_for('train', train => stash('train'))->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), hafas => param('hafas'), date => $departure->{date}->clone->add(days => 1)->strftime('%d.%m.%Y'), highlight => param('highlight') // stash('station')}) %>">▶</a>
+% }
+% }
+ <ul class="mroute">
+% for my $stop (@{$departure->{route_pre_diff}}) {
+% if ($stop->{is_annotated} and $stop->{prod_name}) {
+ <li class="annotation">
+% if ($stop->{prod_name}) {
+%= $stop->{prod_name}
+% }
+% if ($stop->{direction}) {
+ → <%= $stop->{direction} %>
+% }
+% if ($stop->{operator}) {
+ (<%= $stop->{operator} %>)
+% }
+ </li>
+% }
+ <li class="<%= $stop->{isPast} ? 'past-stop' : 'future-stop' %>">
+ <a href="<%= url_for('station', station => $stop->{eva} // $stop->{name})->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), dbris => param('dbris'), efa => param('efa'), hafas => param('hafas')}) %>#<%= ((param('dbris') or param('hafas')) and $departure->{trip_id}) ? ($departure->{trip_id} =~ s{[ #|]}{x}gr) : (($departure->{train_type} // q{x}) . ($departure->{train_no} // q{x})) %>" class="
+% if ($stop->{isAdditional}) {
+ additional-stop
+% }
+% elsif ($stop->{isCancelled}) {
+ cancelled-stop
+% }
+% elsif ($self->is_important($stop->{name})) {
+ important-stop
+% }
+% else {
+ generic-stop
+% }
+% if (($stop->{rt_dep} and $stop->{dep_delay}) or (not $stop->{rt_dep} and $stop->{rt_arr} and $stop->{arr_delay})) {
+ "><span class="time-sched-only"><%= ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') %></span> <span class="time-delayed"><%= ($stop->{rt_dep} // $stop->{rt_arr})->strftime('%H:%M') %></span>
+% }
+% elsif (($stop->{rt_dep} and defined $stop->{dep_delay}) or (not $stop->{rt_dep} and $stop->{rt_arr} and defined $stop->{arr_delay})) {
+ "><span class="time-sched-ontime"><%= ($stop->{sched_dep} // $stop->{sched_arr}) ? ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') : q{} %></span>
+% }
+% else {
+ "><span class="time-sched"><%= ($stop->{sched_dep} // $stop->{sched_arr}) ? ($stop->{sched_dep} // $stop->{sched_arr})->strftime('%H:%M') : q{} %></span>
+% }
+% if ($stop->{tz_offset} and $stop->{local_dt_da}) {
+ (lokal <%= $stop->{local_dt_da}->strftime('%H:%M') %>)
+% }
+ <%= $stop->{name} %></a>
+% if ($stop->{load}{FIRST} or $stop->{load}{SECOND}) {
+% my ($text, $icon1, $icon2) = utilization_icon([$stop->{load}{FIRST}, $stop->{load}{SECOND}]);
+ % if ($icon1 ne 'help_outline') {
+ <i class="material-icons" aria-hidden="true"><%= $icon1 %></i>
+ % }
+ <i class="material-icons" aria-hidden="true"><%= $icon2 %></i>
+% }
+ </li>
+% }
+% if (stash('station_name')) {
+% if ($departure->{is_annotated} and $departure->{prod_name}) {
+ <li class="annotation">
+% if ($departure->{prod_name}) {
+%= $departure->{prod_name}
+% }
+% if ($departure->{direction}) {
+ → <%= $departure->{direction} %>
+% }
+% if ($departure->{operator}) {
+ (<%= $departure->{operator} %>)
+% }
+ </li>
+% }
+ <li class="<%= $departure->{is_cancelled} ? 'cancelled-stop' : q{} %> <%= $departure->{isPast} ? 'past-stop' : 'future-stop' %>">
+% if ($departure->{departure} and $departure->{sched_departure} and $departure->{departure} ne $departure->{sched_departure}) {
+ <span class="time-sched-only"><%= $departure->{sched_departure} // $departure->{sched_arrival} // q{} %></span><span class="time-delayed">
+% }
+% elsif ($departure->{departure} and $departure->{sched_departure} and $departure->{departure} eq $departure->{sched_departure} and not $departure->{no_realtime_yet}) {
+ <span class="time-sched-ontime">
+% }
+% elsif ($departure->{arrival} and $departure->{sched_arrival} and $departure->{arrival} ne $departure->{sched_arrival}) {
+ <span class="time-sched-only"><%= $departure->{sched_departure} // $departure->{sched_arrival} // q{} %></span><span class="time-delayed">
+% }
+% elsif ($departure->{arrival} and $departure->{sched_arrival} and $departure->{arrival} eq $departure->{sched_arrival} and not $departure->{no_realtime_yet}) {
+ <span class="time-sched-ontime">
+% }
+% else {
+ <span class="time-sched">
+% }
+%= $departure->{departure} // $departure->{arrival} // $departure->{sched_departure} // $departure->{sched_arrival} // q{}
+ </span>
+% if ($departure->{tz_offset} and $departure->{local_dt_da}) {
+ (lokal <%= $departure->{local_dt_da}->strftime('%H:%M') %>)
+% }
+ <strong><%= stash('station_name') %></strong>
+% if (my $u = $departure->{utilization}) {
+% my ($text, $icon1, $icon2) = utilization_icon($u);
+ % if ($icon1 ne 'help_outline') {
+ <i class="material-icons" aria-hidden="true"><%= $icon1 %></i>
+ % }
+ <i class="material-icons" aria-hidden="true"><%= $icon2 %></i>
+% }
+ </li>
+% }
+% for my $stop (@{$departure->{route_post_diff}}) {
+% if ($stop->{is_annotated} and $stop->{prod_name}) {
+ <li class="annotation">
+% if ($stop->{prod_name}) {
+%= $stop->{prod_name}
+% }
+% if ($stop->{direction}) {
+ → <%= $stop->{direction} %>
+% }
+% if ($stop->{operator}) {
+ (<%= $stop->{operator} %>)
+% }
+ </li>
+% }
+ <li class="<%= $stop->{isPast} ? 'past-stop' : 'future-stop' %>">
+ <a href="<%= url_for('station', station => $stop->{eva} // $stop->{name})->query({detailed => param('detailed'), past => param('past'), rt => param('rt'), dbris => param('dbris'), efa => param('efa'), hafas => param('hafas')}) %>#<%= ((param('dbris') or param('hafas')) and $departure->{trip_id}) ? ($departure->{trip_id} =~ s{[ #|]}{x}gr) : (($departure->{train_type} // q{x}) . ($departure->{train_no} // q{x})) %>" class="
+% if ($stop->{isAdditional}) {
+ additional-stop
+% }
+% elsif ($stop->{isCancelled}) {
+ cancelled-stop
+% }
+% elsif ($self->is_important($stop->{name})) {
+ important-stop
+% }
+% else {
+ generic-stop
+% }
+% if (($stop->{rt_arr} and $stop->{arr_delay}) or (not $stop->{rt_arr} and $stop->{rt_dep} and $stop->{dep_delay})) {
+ "><span class="time-sched-only"><%= ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') %></span> <span class="time-delayed"><%= ($stop->{rt_arr} // $stop->{rt_dep})->strftime('%H:%M') %></span>
+% }
+% elsif (($stop->{rt_arr} and defined $stop->{arr_delay}) or (not $stop->{rt_arr} and $stop->{rt_dep} and defined $stop->{dep_delay})) {
+ "><span class="time-sched-ontime"><%= ($stop->{sched_arr} // $stop->{sched_dep}) ? ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') : q{} %></span>
+% }
+% else {
+ "><span class="time-sched"><%= ($stop->{sched_arr} // $stop->{sched_dep}) ? ($stop->{sched_arr} // $stop->{sched_dep})->strftime('%H:%M') : q{} %></span>
+% }
+% if ($stop->{tz_offset} and $stop->{local_dt_ad}) {
+ (lokal <%= $stop->{local_dt_ad}->strftime('%H:%M') %>)
+% }
+ <%= $stop->{name} %></a>
+% if ($stop->{load}{FIRST} or $stop->{load}{SECOND}) {
+% my ($text, $icon1, $icon2) = utilization_icon([$stop->{load}{FIRST}, $stop->{load}{SECOND}]);
+ % if ($icon1 ne 'help_outline') {
+ <i class="material-icons" aria-hidden="true"><%= $icon1 %></i>
+ % }
+ <i class="material-icons" aria-hidden="true"><%= $icon2 %></i>
+% }
+ </li>
+% }
+ </ul> <!-- mroute -->
+% }
+% if ($departure->{operators} and @{$departure->{operators} // []}) {
+ <div class="details">Betrieb: <%= join(q{, }, @{ $departure->{operators} // [] } ) %></div>
+% }
+% if ($departure->{details} and @{$departure->{details}}) {
+ <div class="details">Details:
+ <ul>
+% for my $pair (@{$departure->{details}}) {
+ <li>
+% if ($pair->[1]{icon}) {
+ <i class="material-icons"><%= $pair->[1]{icon} %></i>
+% }
+%= $pair->[0]
+% if (length($pair->[0]) > 25) {
+ <br/>
+% }
+ <span class="reason">
+%= $pair->[1]{text}
+ </span>
+ </li>
+% }
+% if ($departure->{route_info}) {
+ <li><%= $departure->{route_info} %></li>
+% }
+ </ul>
+ </div>
+% }
+ </div> <!-- mfooter -->
diff --git a/templates/_wagon.html.ep b/templates/_wagon.html.ep
new file mode 100644
index 0000000..dccecc0
--- /dev/null
+++ b/templates/_wagon.html.ep
@@ -0,0 +1,97 @@
+% my $bg = '';
+% my $extra_class = '';
+% if ($wagon->has_first_class) {
+% $extra_class .= ' firstclass';
+% }
+% if ($wagon->is_locomotive or $wagon->is_powercar) {
+% $extra_class .= ' powercar';
+% }
+% if ($wagon->is_closed) {
+% $extra_class .= ' closed';
+% }
+% if ($group->train_no ne $train_no) {
+% $extra_class .= ' nondestwagon';
+% }
+ <div class="wagon <%= $extra_class %>" style="
+ top: <%= $wagon->start_percent %>%; bottom: <%= 100 - $wagon->end_percent %>%; <%= $bg %>">
+% if ($wagon->is_locomotive or $wagon->is_powercar) {
+% }
+% elsif ($wagon->is_closed) {
+ X
+% }
+% else {
+%= $wagon->number // q{}
+% if ($wagon->has_wheelchair_space) {
+ <i class="material-icons" style="font-size: 20px;">accessible</i>
+% }
+% if ($wagon->has_bistro) {
+ <i class="material-icons">restaurant</i>
+% }
+% if ($wagon->has_quiet_zone) {
+ <i class="tiny material-icons">volume_off</i>
+% }
+% if ($wagon->has_family_zone) {
+ <i class="material-icons">people</i>
+% }
+% if ($wagon->has_bahn_comfort) {
+ <i class="material-icons">star</i>
+% }
+% }
+ <div class="direction">
+% if (not defined $wr->direction) {
+% }
+% elsif ($wr->direction == 100) {
+ <i class="material-icons">arrow_downward</i>
+% }
+% else {
+ <i class="material-icons">arrow_upward</i>
+% }
+ </div>
+ </div>
+ <div class="details" style="
+ top: <%= $wagon->start_percent %>%; bottom: <%= 100 - $wagon->end_percent %>%;">
+% if ($exit_dir ne 'right') {
+% if (my $img = wagon_image($wr->train_type // '?', $wagon->type, $wagon->uic_id)) {
+ <a class="type" href="/w/<%= $img %>?n=<%= $wagon->number // '' %>&amp;s=<%= $wagon->section %>&amp;r=<%= $wref %>"><%= $wagon->type %></a>
+% }
+% else {
+ <span class="type">
+%= $wagon->type
+ </span>
+% }
+% }
+% my $uic_id = $wagon->uic_id;
+% if (length($uic_id) != 12 and length($uic_id) != 14) {
+ <span class="uicunknown"><%= $uic_id %></span>
+% }
+% elsif (substr($uic_id, 0, 2) >= 90) {
+ <span class="uicexchange"><%= substr($uic_id, 0, 2) %></span><span class="uiccountry"><%= substr($uic_id, 2, 2) %></span><span class="uic5"><%= substr($uic_id, 4, 1) %></span><span class="uictype"><%= substr($uic_id, 5, 3) %></span><span class="uicno"><%= substr($uic_id, 8, 3) %></span><span class="uiccheck"><%= substr($uic_id, 11) %></span>
+% }
+% else {
+ <span class="uicexchange"><%= substr($uic_id, 0, 2) %></span><span class="uiccountry"><%= substr($uic_id, 2, 2) %></span><span class="uic56"><%= substr($uic_id, 4, 2) %></span><span class="uic78"><%= substr($uic_id, 6, 2) %></span><span class="uicno"><%= substr($uic_id, 8, 3) %></span><span class="uiccheck"><%= substr($uic_id, 11) %></span>
+% }
+% if ($exit_dir eq 'right') {
+% if (my $img = wagon_image($wr->train_type // '?', $wagon->type, $wagon->uic_id)) {
+ <a class="type" href="/w/<%= $img %>?n=<%= $wagon->number // '' %>&amp;s=<%= $wagon->section %>&amp;r=<%= $wref %>"><%= $wagon->type %></a>
+% }
+% else {
+ <span class="type">
+%= $wagon->type
+ </span>
+% }
+% }
+% if ($multi and $first) {
+ <br/>
+ <span class="groupno">
+% if (scalar $wr->train_numbers > 1) {
+ <%= $group->train_type %> <%= $group->train_no %>
+% }
+% if (scalar $wr->destinations > 1) {
+ → <%= $group->destination %>
+% }
+ </span>
+ % if ($multi and $group->desc_short) {
+ <span class="grouptype"><%= $group->desc_short %></span>
+% }
+% }
+ </div>
diff --git a/templates/about.html.ep b/templates/about.html.ep
new file mode 100644
index 0000000..3bf8295
--- /dev/null
+++ b/templates/about.html.ep
@@ -0,0 +1,58 @@
+<div class="container">
+ <p>
+ DBF ist ein inoffizieller Abfahrtsmonitor für Nah-, Regional- und Fernverkehr in Deutschland und Umgebung.
+ Die Fahrten in der Übersicht verlinken je eine Detailseite mit Unterwegshalten, Meldungen und Kartendarstellung.
+ Bei HAFAS-Backends ist zusätzlich die Suche nach spezifischen Fahrten möglich.
+ </p>
+ <p>
+ Der <a href="<%= app->config->{'source_url'} %>">Quelltext</a> steht unter der <a href="https://git.finalrewind.org/db-fakedisplay/tree/COPYING">GNU AGPL v3</a> als Open Source zur Verfügung. © 2011 – 2024 <a href="https://finalrewind.org">derf</a>.
+ % if (my $issue_url = app->config->{'issue_url'}) {
+ Fehlermeldungen bitte via
+ <a href="<%= $issue_url %>">Issue Tracker</a>.
+ % }
+ Alle von DBF referenzierten Informationen können auch direkt per CLI im Text- oder JSON-Format abgerufen werden – die unten verlinkten Backends beinhalten entsprechende Anwendungen.
+ </p>
+ <p>
+ Diese Installation nutzt
+ <strong>DBF v<%= stash('version') // '???' %></strong> mit folgenden Backends:
+ <ul>
+ <li>Innerdeutscher Regional- und Fernverkehr: DB IRIS via <a href="https://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a>
+ <strong>v<%= $Travel::Status::DE::IRIS::VERSION %></strong></li>
+ <li>Nah-, Regional- und Fernverkehr im In- und Ausland: bahn.de via <a href="https://finalrewind.org/projects/Travel-Status-DE-DBRIS/">Travel::Status::DE::DBRIS</a>
+ <strong>v<%= $Travel::Status::DE::DBRIS::VERSION %></strong></li>
+ <li>Nah-, Regional- und Fernverkehr im In- und Ausland: EFA via <a href="https://finalrewind.org/projects/Travel-Status-DE-VRR/">Travel::Status::DE::EFA</a>
+ <strong>v<%= $Travel::Status::DE::EFA::VERSION %></strong></li>
+ <li>Nah-, Regional- und Fernverkehr im In- und Ausland: HAFAS via <a href="https://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/">Travel::Status::DE::HAFAS</a>
+ <strong>v<%= $Travel::Status::DE::HAFAS::VERSION %></strong></li>
+ <li>Nah-, Regional- und Fernverkehr im In- und Ausland: MOTIS via <a href="https://finalrewind.org/projects/Travel-Status-MOTIS/">Travel::Status::MOTIS</a>
+ <strong>v<%= $Travel::Status::MOTIS::VERSION %></strong></li>
+ </ul>
+ </p>
+ <p>
+ Verwendete Ressourcen:
+ <ul>
+ <li><a href="/_backend">HAFAS-Backends</a> via <a href="https://github.com/public-transport/transport-apis">transport-apis</a>, CC0</li>
+ <li><a href="https://data.deutschebahn.com/dataset/zugbildungsplanzugbildungsplan-zpar">Zugbildungsplan</a> © DB Fernverkehr AG, lizensiert unter CC-BY 4.0</li>
+ <li><a href="http://data.deutschebahn.com/dataset/data-haltestellen">Haltestellenliste</a>
+ © DB Station&amp;Service AG,
+ Europaplatz 1,
+ 10557 Berlin, lizensiert unter CC-BY 4.0</li>
+ <li><a href="https://data.deutschebahn.com/dataset/fahrzeuglexikon">Fahrzeuglexikon</a>
+ © DB Fernverkehr AG, lizensiert unter CC-BY 4.0; Abbildungen © Seemanngrafik d.i.p. im Auftrag der Deutschen Bahn AG, lizensiert unter CC-BY-SA 4.0</li>
+ </ul>
+ </p>
+ <p>
+ Trivia: Das Projekt begann als „db-fakedisplay“ (kurz dbf) zur
+ Nachahmung von Bahnhofs-Abfahrtstafeln. Inzwischen liegt der Fokus auf
+ der Bereitstellung von Informationen für mobile und Desktop-Anwendungen
+ und die Bezeichnung DBF wurde zum Eigennamen ohne weitere Bedeutung.
+ </p>
+</div>
+
+% if (-e 'templates/imprint.html.ep') {
+%= include 'imprint'
+% }
+
+% if (-e 'templates/privacy.html.ep') {
+%= include 'privacy'
+% }
diff --git a/templates/app.html.ep b/templates/app.html.ep
new file mode 100644
index 0000000..8b52c61
--- /dev/null
+++ b/templates/app.html.ep
@@ -0,0 +1,214 @@
+% if (@{$departures}) {
+
+% if (not param('ajax')) {
+<div class="app" data-station="<%= $station %>">
+<div class="moreinfo collapsed-moreinfo">
+ <div class="mheader">
+ <div>
+ <span class="train-line"></span>
+ <span class="train-no"></span>
+ </div>
+ <div>
+ <span class="train-origin"></span>
+ →
+ <span class="train-dest"></span>
+ </div>
+ </div>
+ <div class="mfooter"></div>
+</div>
+<ul>
+% } # not param('ajax')
+% my $i = 0;
+% my $dt_now = DateTime->now;
+% for my $departure (@{$departures}) {
+% $i++;
+% my $route_str = q{};
+% my $via_max = @{$departure->{via} // []};
+% my $via_cur = 0;
+% for my $stop (@{$departure->{via} // []}) {
+% $via_cur++;
+% $route_str .= $stop . ($via_cur < $via_max ? ' - ' : q{});
+% }
+ <li
+% if (param('dbris') or param('hafas') or param('efa')) {
+ data-jid="<%= $departure->{journey_id} =~ s{#}{%23}gr %>"
+% }
+ data-train="<%= ($departure->{train_type} // q{}) %> <%= ($departure->{train_no} // $departure->{train} // q{}) %>"
+ data-line="<%= $departure->{train_type} %> <%= $departure->{train_line} // $departure->{train_no} %>"
+ data-no="<%= $departure->{train_line} ? $departure->{train_no} : q{} %>"
+ data-linetype="<%= $departure->{linetype} %>"
+ data-from="<%= $departure->{origin} // q{???} %>"
+ data-to="<%= $departure->{destination} // q{???} %>"
+ data-station="<%= $departure->{station} // $station %>"
+ data-platform="<%= $departure->{scheduled_platform} // $departure->{platform} // '' %>"
+ data-arrival="<%= $departure->{sched_arrival} // '' %>"
+ data-departure="<%= $departure->{sched_departure} // '' %>"
+ data-moreinfo="<%= join(q{|}, map { ($_->[0]->isa('DateTime') ? $_->[0]->strftime('%H:%M') . ' ' . $_->[1] : $_->[0] . ' ' . $_->[1]{text}) } @{ $departure->{moreinfo} // [] } ) %>"
+ data-routeprev="<%= join(q{|}, @{ $departure->{route_pre} // [] } ) %>"
+ data-routenext="<%= join(q{|}, @{ $departure->{route_post} // [] } ) %>"
+% if ($departure->{is_cancelled} or $departure->{departure_is_cancelled}) {
+ class="cancelled">
+% }
+% else {
+ >
+% }
+% if (param('hafas')) {
+ <a href="/z/<%= Mojo::Util::url_escape($departure->{journey_id}) . '?hafas=' . Mojo::Util::url_escape(param('hafas')) . '&amp;highlight=' . Mojo::Util::url_escape($departure->{station} // $station) %>">
+% }
+% elsif (param('efa')) {
+ <a href="/z/<%= Mojo::Util::url_escape($departure->{journey_id}) . '?efa=' . Mojo::Util::url_escape(param('efa')) . '&amp;highlight=' . Mojo::Util::url_escape($departure->{station} // $station) %>">
+% }
+% elsif (param('dbris')) {
+ <a href="/z/<%= Mojo::Util::url_escape($departure->{journey_id}) . '?dbris=' . Mojo::Util::url_escape(param('dbris')) . '&amp;highlight=' . Mojo::Util::url_escape($departure->{station} // $station) %>">
+% }
+% else {
+ <a href="/z/<%= Mojo::Util::url_escape(($departure->{train_type} // q{}) . ' ' . ($departure->{train_no} // $departure->{train} // q{})) . '/' . Mojo::Util::url_escape($departure->{station} // $station) %>">
+% }
+% if (param('dbris') or param('hafas')) {
+ <div class="anchor" id="<%= $departure->{journey_id} =~ s{[ #|]}{x}gr %>"></div>
+% }
+% else {
+ <div class="anchor" id="<%= ($departure->{train_type} // q{x}) . ($departure->{train_no} // q{x}) %>"></div>
+% }
+ <div class="line <%= $departure->{linetype} %>">
+ % if ($departure->{train_type} and $departure->{train_no}) {
+%= $departure->{train_type}
+% }
+% if ($departure->{train_line} ) {
+%= $departure->{train_line}
+% }
+% elsif ($departure->{train_no}) {
+ <span class="trainno"><%= $departure->{train_no} %></span>
+% }
+% else {
+%= $departure->{train}
+% }
+% if ($departure->{train_line} and $departure->{train_no}
+% and $departure->{train_line} ne $departure->{train_no}
+% and param('detailed')) {
+ <div class="trainno_sub"><%= $departure->{train_no} %></div>
+% }
+ </div>
+% if (@{$departure->{replaced_by}} or @{$departure->{replacement_for}}) {
+ <div class="lineinfo">
+% if (@{$departure->{replaced_by}}) {
+ %#<span class="replaced">→ Ersatzzug</span>
+% }
+% if (@{$departure->{replacement_for}}) {
+ <span class="replacement">Ersatzzug</span>
+% }
+ </div>
+% }
+% if ($departure->{sched_departure}) {
+ <span class="dest">
+ <span class="visually-hidden">nach</span>
+%= $departure->{destination}
+ </span>
+% }
+% else {
+ <span class="origin">
+ <span class="visually-hidden">von</span>
+%= $departure->{origin}
+ </span>
+% }
+ <span class="time <%= $show_realtime ? get_rt_time_class($departure) : q{} %>">
+% if ($departure->{delay} and not $departure->{is_cancelled} and not $departure->{departure_is_cancelled}) {
+% if ($show_realtime and ($departure->{sched_arrival} or $departure->{sched_departure})) {
+% if ($departure->{delay} > ($hide_low_delay ? 4 : 0)) {
+ <span class="delaynorm" aria-hidden="true"><%= $departure->{sched_departure} // $departure->{sched_arrival} %> ⇒</span>
+% }
+% elsif ($departure->{delay} < 0) {
+ <span class="undelaynorm" aria-hidden="true"><%= $departure->{sched_departure} // $departure->{sched_arrival} %> ⇒</span>
+% }
+% }
+% else {
+% if ($departure->{delay} > ($hide_low_delay ? 4 : 0)) {
+ <span class="delay" aria-hidden="true">+<%= $departure->{delay} %></span>
+% }
+% elsif ($departure->{delay} < 0) {
+ <span class="undelay" aria-hidden="true"><%= $departure->{delay} %></span>
+% }
+% }
+% }
+% elsif ($departure->{missing_realtime}) {
+ <span class="visually-hidden">Echtzeitdaten fehlen</span>
+ <span class="no-realtime" aria-hidden="true"><i class="material-icons">gps_off</i></span>
+% }
+% if (param('detailed')) {
+% my $arrow = '→';
+% if (not $departure->{sched_arrival}) {
+% $arrow = '↦';
+% }
+% elsif (not $departure->{sched_departure}) {
+% $arrow = '⇥';
+% }
+% if ($show_realtime) {
+%= ($departure->{arrival} // q{}) . $arrow . ($departure->{departure} // q{})
+% }
+% else {
+%= ($departure->{sched_arrival} // q{}) . $arrow . ($departure->{sched_departure} // q{})
+% }
+% }
+% else {
+% if ($departure->{is_cancelled} or $departure->{departure_is_cancelled}) {
+%= $departure->{sched_departure} // $departure->{sched_arrival} // $departure->{time}
+% }
+% else {
+%= $departure->{time}
+% }
+% }
+ </span>
+% if (($departure->{scheduled_platform} and $departure->{platform} and
+% $departure->{scheduled_platform} ne $departure->{platform})
+% or $departure->{changed_platform}) {
+ <span class="platform changed-platform">
+% }
+% else {
+ <span class="platform">
+% }
+% if ($departure->{load}{FIRST} or $departure->{load}{SECOND}) {
+% my ($text, $icon1, $icon2) = utilization_icon([$departure->{load}{FIRST}, $departure->{load}{SECOND}]);
+ <span class="load">
+ <i class="material-icons" style="vertical-align: bottom;" aria-hidden="true"><%= $icon2 %></i>
+ </span>
+% }
+% elsif (my $o = $departure->{occupancy}) {
+ <span class="load">
+% my ($text, $icon) = occupancy_icon($o);
+ <i class="material-icons" style="vertical-align: bottom;" aria-hidden="true"><%= $icon %></i>
+ </span>
+% }
+ <span class="visually-hidden">Gleis</span>
+%= $departure->{platform}
+ </span>
+% if ($departure->{info} and length $departure->{info}) {
+ <span class="info">
+%= $departure->{info}
+ </span>
+% }
+% else {
+ <span class="route">
+ <span class="visually-hidden">über</span>
+%= $route_str
+ </span>
+% }
+ </a>
+ </li>
+
+% }
+% if (not param('ajax')) {
+
+ </ul>
+ </div> <!-- app -->
+% }
+
+% }
+% elsif (not param('ajax')) {
+
+<div class="container">
+<div class="error"><strong>Keine Abfahrten gefunden.</strong>
+Möglicherweise ist der Filter zu restriktiv, oder an dieser Station fahren
+momentan keine Züge.</div>
+</div> <!-- container -->
+
+% }
diff --git a/templates/clean.html.ep b/templates/clean.html.ep
deleted file mode 100644
index 43f3c54..0000000
--- a/templates/clean.html.ep
+++ /dev/null
@@ -1,240 +0,0 @@
-% if (@{$departures}) {
-
-<div class="displayclean">
-<ul>
-% my $i = 0;
-% my $dt_now = DateTime->now;
-% for my $departure (@{$departures}) {
-% $i++;
-% my $route_str = q{};
-% my $via_max = @{$departure->{via} // []};
-% my $via_cur = 0;
-% for my $stop (@{$departure->{via} // []}) {
-% $via_cur++;
-% $route_str .= $stop . ($via_cur < $via_max ? ' - ' : q{});
-% }
-% my $extraclasses = q{};
-% if ($departure->{is_cancelled}) {
-% $extraclasses .= ' cancelled';
- <li class="cancelled">
-% }
-% else {
- <li>
-% }
-% my $linetype = q{};
-% if ( $departure->{train_type} eq 'S' ) {
-% $linetype = 'sbahn';
-% }
- <div class="line <%= $linetype %>">
- % if ($departure->{train_type} and $departure->{train_no}) {
-%= $departure->{train_type}
-% }
-% if ($departure->{train_line} ) {
-%= $departure->{train_line}
-% }
-% elsif ($departure->{train_no}) {
- <span class="trainno"><%= $departure->{train_no} %></span>
-% }
-% else {
-%= $departure->{train}
-% }
- </div>
-% if (@{$departure->{replaced_by}} or @{$departure->{replacement_for}}) {
- <div class="lineinfo">
-% for my $replacement (@{$departure->{replaced_by}}) {
- <span class="replaced">→ <%= $replacement %></span>
-% }
-% for my $replacement (@{$departure->{replacement_for}}) {
- <span class="replacement">↑ <%= $replacement %></span>
-% }
- </div>
-% }
- <div class="moreinfo">
- <div class="mheader">
-% if ($departure->{train_no} or $departure->{line_no}) {
- <span class="train-line"><%= $departure->{train_type} // q{???} %></span>
-% if ($departure->{train_line}) {
- <span class="train-no"><%= $departure->{train_no} // q{???} %></span>
-% }
-% else {
- <span class="train-line"><%= $departure->{train_no} // q{???} %></span>
-% }
-% }
-% else {
- <span class="train-line"><%= $departure->{train} // q{???} %></span>
-% }
-% if ($departure->{origin}) {
- :
- <span class="train-origin"><%= $departure->{origin} %></span>
-% }
- →
- <span class="train-dest"><%= $departure->{destination} // q{???} %></span>
-% if ($departure->{is_cancelled}) {
- <div class="minfo">Fahrt fällt aus</div>
-% }
-% elsif (defined $departure->{delay} and $departure->{delay} > 0) {
- <div class="minfo">+<%= $departure->{delay} %></div>
-% }
- </div> <!-- mheader -->
-% if (not $departure->{is_cancelled}) {
- <div class="timeinfo">
-% if ($departure->{sched_arrival}) {
- Ankunft: <%= $departure->{sched_arrival} %>
-% if ($departure->{arrival} and $departure->{arrival} ne $departure->{sched_arrival}) {
- (heute <%= $departure->{arrival} %>)
-% }
- <br/>
-% }
-% if ($departure->{sched_departure}) {
- Abfahrt: <%= $departure->{sched_departure} %>
-% if ($departure->{departure} and $departure->{departure} ne $departure->{sched_departure}) {
- (heute <%= $departure->{departure} %>)
-% }
- <br/>
-% }
-% if (not ($departure->{sched_arrival} or $departure->{sched_departure})) {
- Abfahrt: <%= $departure->{time} %>
-% if ($departure->{delay}) {
- (heute +<%= $departure->{delay} %>)
-% }
-% }
- </div> <!-- timeinfo -->
-% }
-% if ($departure->{route_post_diff} and @{$departure->{route_post_diff}}) {
- <div class="mroute">
- Über:
-% my $first = 0;
-% for my $stop (@{$departure->{route_post_diff} // q{???}}) {
-% if ($first++) {
- <span class="separator">–</span>
-% }
-% if ($stop->{isAdditional}) {
- <span class="additional-stop"><%= $stop->{name} %></span>
-% }
-% elsif ($stop->{isCancelled}) {
- <span class="cancelled-stop"><%= $stop->{name} %></span>
-% }
-% elsif ($self->is_important($stop->{name})) {
- <span class="important-stop"><%= $stop->{name} %></span>
-% }
-% else {
- <span class="generic-stop"><%= $stop->{name} %></span>
-% }
-% }
- </div> <!-- mroute -->
-% }
-% elsif ($departure->{route_timetable}) {
- <div class="mroute">
- Über:
-% my $first = 0;
-% for my $stop (@{$departure->{route_timetable}}) {
-% my ($time, $name) = @{$stop};
-% if ($first++) {
- <span class="separator">–</span>
-% }
- <span class="generic-stop"><%= $time %></span>
-% if ($self->is_important($name)) {
- <span class="important-stop"><%= $name %></span>
-% }
-% else {
- <span class="generic-stop"><%= $name %></span>
-% }
-% }
- </div> <!-- mroute -->
-% }
-% if ($departure->{moreinfo} and @{$departure->{moreinfo}}) {
- Meldungen:
- <ul>
-% for my $pair (@{$departure->{moreinfo}}) {
- <li>
-% if ($pair->[0]->isa('DateTime')) {
-% if ($pair->[0]->day != $dt_now->day) {
-% $pair->[0]->set_locale('de_DE');
-%= $pair->[0]->strftime('%a %H:%M')
-% }
-% else {
-%= $pair->[0]->strftime('%H:%M')
-% }
-% }
-% else {
-%= $pair->[0]
-% }
- :
- <span class="reason">
-%= $pair->[1]
- </span>
- </li>
-% }
-% if ($departure->{route_info}) {
- <li><%= $departure->{route_info} %></li>
-% }
- </ul>
-% }
- </div> <!-- moreinfo -->
-% if ($departure->{info} and length $departure->{info}) {
- <span class="info">
-%= $departure->{info}
- </span>
-% }
-% else {
- <span class="route">
-%= $route_str
- </span>
-% }
- <span class="dest <%= $extraclasses %>">
-%= $departure->{destination}
- </span>
- <span class="countdown <%= $extraclasses %>">
-% if ($departure->{delay} and not $departure->{is_cancelled}) {
-% if ($show_realtime) {
-% if ($departure->{delay} > 0) {
- <span class="delaynorm">(+<%= $departure->{delay} %>)</span>
-% }
-% else {
- <span class="undelaynorm">(<%= $departure->{delay} %>)</span>
-% }
-% }
-% else {
-% if ($departure->{delay} > 0) {
- <span class="delay">(+<%= $departure->{delay} %>)</span>
-% }
-% else {
- <span class="undelay">(<%= $departure->{delay} %>)</span>
-% }
-% }
-% }
-% if ($departure->{scheduled_platform} and $departure->{platform} and
-% $departure->{scheduled_platform} ne $departure->{platform}) {
- <span class="platform changed-platform">
-% }
-% else {
- <span class="platform">
-% }
-%= $departure->{platform}
- </span>
- </span>
- <span class="time <%= ($show_realtime and $departure->{delay} and not
- $departure->{is_cancelled}) ? 'delayed' : q{} %> <%= $extraclasses %>">
-% if ($show_realtime and $departure->{delay} and not $departure->{is_cancelled}) {
- <span class="delayed">
-% }
-%= $departure->{time}
- </span>
-% if ($show_realtime and $departure->{delay} and not $departure->{is_cancelled}) {
- </span>
-% }
- </li>
-
-% }
-
- </ul>
- </div> <!-- displayclean -->
-
-% }
-% else {
-
-<p>
-<div class="error">No matching departures</div>
-</p>
-
-% }
diff --git a/templates/coverage_map.html.ep b/templates/coverage_map.html.ep
new file mode 100644
index 0000000..bd3d94c
--- /dev/null
+++ b/templates/coverage_map.html.ep
@@ -0,0 +1,22 @@
+<div class="container">
+ Das <%= $backend %>-Backend „<%= $service %>“ liefert ungefähr innerhalb
+ der folgenden grob umrissenen Region voraussichtlich nützliche Echtzeitdaten.
+</div>
+
+<div class="container">
+ <div id="map" style="height: 70vh;">
+ </div>
+</div>
+
+<script>
+const map = L.map('map').setView([51.306, 9.712], 6);
+
+L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+}).addTo(map);
+
+const coverage = L.geoJSON(<%== $coverage %>);
+
+coverage.addTo(map);
+map.fitBounds(coverage.getBounds());
+</script>
diff --git a/templates/exception.html.ep b/templates/exception.html.ep
index 68d19fd..7654c0b 100644
--- a/templates/exception.html.ep
+++ b/templates/exception.html.ep
@@ -1,14 +1,17 @@
-<div class="error">500 Internal Server Error</div>
-<div>
-<pre>
-Beim Bearbeiten der Anfrage ist ein Fehler aufgetreten.
-
-An error occured while processing your request.
+<div class="error">
+<strong>500 Internal Server Error:</strong>
+Beim Bearbeiten der Anfrage ist ein Fehler aufgetreten.<br/>
+<pre>
----------[Debug start]----------
-%= $exception->message
+% if ($exception) {
+%= ref($exception) ? $exception->message : $exception
Stash:
%= dumper $snapshot
+% }
+% else {
+%= stash('message')
+% }
----------[Debug end]----------
</pre>
</div>
diff --git a/templates/geostop.html.ep b/templates/geostop.html.ep
new file mode 100644
index 0000000..843892d
--- /dev/null
+++ b/templates/geostop.html.ep
@@ -0,0 +1,5 @@
+<div class="geolocation">
+<div class="candidateheader">Nächstgelegene Stationen:</div>
+<div class="candidatestatus">Bitte warten…</div>
+<div class="candidatelist"></div>
+</div> <!-- geolocation -->
diff --git a/templates/infoscreen.html.ep b/templates/infoscreen.html.ep
new file mode 100644
index 0000000..85d0f8d
--- /dev/null
+++ b/templates/infoscreen.html.ep
@@ -0,0 +1,158 @@
+% if (@{$departures}) {
+
+% if (param('dark')) {
+<div class="infoscreen infoscreendark">
+% }
+% else {
+<div class="infoscreen infoscreenlight">
+% }
+<ul>
+% my $i = 0;
+% my $dt_now = DateTime->now;
+% for my $departure (@{$departures}) {
+% $i++;
+% my $route_str = q{};
+% my $via_max = @{$departure->{via} // []};
+% my $via_cur = 0;
+% for my $stop (@{$departure->{via} // []}) {
+% $via_cur++;
+% $route_str .= $stop . ($via_cur < $via_max ? ' - ' : q{});
+% }
+% my $extraclasses = q{};
+% if ($departure->{is_cancelled} or $departure->{departure_is_cancelled}) {
+% $extraclasses .= ' cancelled';
+ <li class="cancelled">
+% }
+% else {
+ <li>
+% }
+% my $linetype = 'bahn';
+% if ( $departure->{train_type} eq 'S' ) {
+% $linetype = 'sbahn';
+% }
+% elsif ( $departure->{train_type} eq 'IC'
+% or $departure->{train_type} eq 'ICE'
+% or $departure->{train_type} eq 'EC'
+% or $departure->{train_type} eq 'ECE'
+% or $departure->{train_type} eq 'EN') {
+% $linetype = 'fern';
+% }
+% elsif ( $departure->{train_type} eq 'THA'
+% or $departure->{train_type} eq 'TGV'
+% or $departure->{train_type} eq 'FLX'
+% or $departure->{train_type} eq 'NJ') {
+% $linetype = 'ext';
+% }
+% elsif ( $departure->{train_line} and $departure->{train_line} =~ m{^S\d} ) {
+% $linetype = 'sbahn';
+% }
+ <div class="line <%= $linetype %>">
+ % if ($departure->{train_type} and $departure->{train_no}) {
+%= $departure->{train_type}
+% }
+% if ($departure->{train_line} ) {
+%= $departure->{train_line}
+% }
+% elsif ($departure->{train_no}) {
+ <span class="trainno"><%= $departure->{train_no} %></span>
+% }
+% else {
+%= $departure->{train}
+% }
+% if ($departure->{train_line} and $departure->{train_no}
+% and $departure->{train_line} ne $departure->{train_no}
+% and param('detailed')) {
+ <div class="trainno_sub"><%= $departure->{train_no} %></div>
+% }
+ </div>
+% if (@{$departure->{replaced_by}} or @{$departure->{replacement_for}}) {
+ <div class="lineinfo">
+% if (@{$departure->{replaced_by}}) {
+ %#<span class="replaced">→ Ersatzzug</span>
+% }
+% if (@{$departure->{replacement_for}}) {
+ <span class="replacement">Ersatzzug</span>
+% }
+ </div>
+% }
+% if ($departure->{info} and length $departure->{info}) {
+ <span class="info">
+%= $departure->{info}
+ </span>
+% }
+% else {
+ <span class="route">
+%= $route_str
+ </span>
+% }
+ <span class="dest <%= $extraclasses %>">
+%= $departure->{destination}
+ </span>
+ <span class="countdown <%= $extraclasses %>">
+% if ($departure->{delay} and not $departure->{is_cancelled}) {
+% if ($show_realtime) {
+% if ($departure->{delay} > 0) {
+ <span class="delaynorm">(+<%= $departure->{delay} %>)</span>
+% }
+% else {
+ <span class="undelaynorm">(<%= $departure->{delay} %>)</span>
+% }
+% }
+% else {
+% if ($departure->{delay} > 0) {
+ <span class="delay">(+<%= $departure->{delay} %>)</span>
+% }
+% else {
+ <span class="undelay">(<%= $departure->{delay} %>)</span>
+% }
+% }
+% }
+% if (($departure->{scheduled_platform} and $departure->{platform} and
+% $departure->{scheduled_platform} ne $departure->{platform})
+% or $departure->{changed_platform}) {
+ <span class="platform changed-platform">
+% }
+% else {
+ <span class="platform">
+% }
+%= $departure->{platform}
+ </span>
+ </span>
+ <span class="time <%= ($show_realtime and $departure->{delay} and not
+ $departure->{is_cancelled}) ? 'delayed' : q{} %> <%= $extraclasses %>">
+% if (param('detailed')) {
+% my $arrow = '→';
+% if (not $departure->{sched_arrival}) {
+% $arrow = '↦';
+% }
+% elsif (not $departure->{sched_departure}) {
+% $arrow = '⇥';
+% }
+% if ($show_realtime) {
+%= ($departure->{arrival} // q{}) . $arrow . ($departure->{departure} // q{})
+% }
+% else {
+%= ($departure->{sched_arrival} // q{}) . $arrow . ($departure->{sched_departure} // q{})
+% }
+% }
+% else {
+%= $departure->{time}
+% }
+ </span>
+ </li>
+
+% }
+
+ </ul>
+ </div> <!-- infoscreen -->
+
+% }
+% else {
+
+<div class="container">
+<div class="error"><strong>Keine Abfahrten gefunden.</strong>
+Möglicherweise ist der Filter zu restriktiv, oder an dieser Station fahren
+momentan keine Züge.</div>
+</div> <!-- container -->
+
+% }
diff --git a/templates/landingpage.html.ep b/templates/landingpage.html.ep
index c7b23f2..80fd34f 100644
--- a/templates/landingpage.html.ep
+++ b/templates/landingpage.html.ep
@@ -1 +1,31 @@
-<!-- landing page contains nothing at the moment -->
+% if (stash 'show_intro') {
+<div class="container">
+% if (0) {
+ <p>
+ DBF is an unofficial departure monitor for regional and long-distance trains within Germany, aiming to combine multiple data sources in a useful manner.
+ It also has limited support for local transit and traffic outside of Germany.
+ </p>
+ <p>
+ This site is operated by a private entity in a not-for-profit manner.
+ There are no uptime or reliability guarantees whatsoever.
+ </p>
+% }
+% else {
+ <p>
+ DBF ist ein inoffizieller Abfahrtsmonitor für Nah-, Regional- und Fernverkehr in Deutschland und Umgebung mit dem Ziel, Daten aus verschiedenen Quellen zusammenzutragen.
+ Es unterstützt neben Fahrten im Netz der DB InfraGO diverse Nah- und Fernverkehrsunternehmen mit EFA- und HAFAS-Backends.
+ Die Fahrten in der Übersicht verlinken je eine Detailseite mit Unterwegshalten, Meldungen und Kartendarstellung.
+ </p>
+ <p>
+ Diese Seite ist ein kostenfreies, privat betriebenes Projekt ohne Verfügbarkeitsgarantie.
+ Alle Angaben ohne Gewähr.
+ </p>
+% }
+<p class="geolink">
+<a class="button" href="<%= url_for('_autostop')->to_abs->scheme('https')->query({efa => param('efa'), hafas => param('hafas')}) %>">Stationen in der Umgebung suchen</a>
+</p>
+<p>
+Oder hier eine Station angeben:
+</p>
+</div>
+% }
diff --git a/templates/layouts/app.html.ep b/templates/layouts/app.html.ep
new file mode 100644
index 0000000..c557bee
--- /dev/null
+++ b/templates/layouts/app.html.ep
@@ -0,0 +1,320 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+ <title><%= stash('title') // 'DBF' %></title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="keywords" content="Abfahrtsmonitor, Bahnhofstafel, Abfahrten, Abfahrtstafel, Nahverkehr, Regionalverkehr, Fernverkehr, ICE, IC, RE, RB, S-Bahn">
+ <meta name="description" content="<%= stash('description') // 'Inoffizieller Abfahrtsmonitor für Nah-, Reginol- und Fernverkehr' %>">
+ <meta name="theme-color" content="#00838f">
+ <link rel="icon" type="image/png" href="/static/icons/icon-16x16.png" sizes="16x16">
+ <link rel="icon" type="image/png" href="/static/icons/icon-32x32.png" sizes="32x32">
+ <link rel="icon" type="image/png" href="/static/icons/icon-96x96.png" sizes="96x96">
+ <link rel="apple-touch-icon" href="/static/icons/icon-120x120.png">
+ <link rel="apple-touch-icon" sizes="180x180" href="/static/icons/icon-180x180.png">
+ <link rel="apple-touch-icon" sizes="152x152" href="/static/icons/icon-152x152.png">
+ <link rel="apple-touch-icon" sizes="167x167" href="/static/icons/icon-167x167.png">
+% if ($self->stash('refresh_interval')) {
+ <meta http-equiv="refresh" content="<%= $self->stash('refresh_interval') %>"/>
+% }
+
+ % my $av = 'v110'; # asset version
+ % if (session('theme') and session('theme') eq 'dark' or param('dark')) {
+ %= stylesheet "/static/${av}/css/dark.min.css", id => 'theme'
+ % }
+ % else {
+ %= stylesheet "/static/${av}/css/light.min.css", id => 'theme'
+ % }
+ <script>
+ function addStyleSheet(name, id) {
+ const path = '/static/<%=$av%>/css/' + name + '.min.css';
+ const old = document.getElementById(id);
+ if (old && (old.href != path)) {
+ old.href = path;
+ document.cookie = 'theme=' + name + ';SameSite=None;Secure';
+ }
+ }
+ const otherTheme = {
+ 'dark': 'light',
+ 'light': 'dark',
+ };
+ var currentTheme = localStorage.getItem('theme');
+ if (!otherTheme.hasOwnProperty(currentTheme)) {
+ currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ }
+ addStyleSheet(currentTheme, 'theme');
+ </script>
+ %= stylesheet "/static/${av}/css/material-icons.css"
+ %= stylesheet "/static/${av}/css/jquery-ui.min.css"
+ %= javascript '/static/js/jquery-3.4.1.min.js', defer => undef
+ %= javascript "/static/${av}/js/jquery-ui.min.js", defer => undef
+ %= javascript "/static/${av}/js/dbf.min.js", defer => undef
+ % if (not stash('hide_opts')) {
+ %= javascript "/dyn/${av}/autocomplete.js", defer => undef
+ % }
+ % if (stash('with_geostop')) {
+ %= javascript "/static/${av}/js/geostop.min.js", defer => undef
+ % }
+ % if (stash('with_map')) {
+ %= stylesheet "/static/${av}/leaflet/leaflet.css"
+ %= javascript "/static/${av}/leaflet/leaflet.js"
+ %= javascript "/static/${av}/js/map-refresh.min.js", defer => undef
+ % }
+</head>
+<body>
+
+<div class="navbar-fixed">
+ <nav style="color: #ffffff; background-color: #00838f;">
+ <div class="nav-wrapper container">
+ % if (my $nav_link = stash('nav_link')) {
+ <a class="brand-logo" style="float: left;" href="<%= $nav_link %>">
+ %= stash('title') || 'DBF'
+ </a>
+ % }
+ % else {
+ <span class="brand-logo">
+ %= stash('title') || 'DBF'
+ </span>
+ % }
+ <ul id="nav-mobile" style="float: right;">
+ % if (stash('api_link')) {
+ <li class="waves-effect waves-light">
+ <a href="<%= stash('api_link') %>"><span class="visually-hidden"><%= stash('api_text') %></span><i class="material-icons" aria-hidden="true"><%= stash('api_icon') %></i></a>
+ </li>
+ % }
+ % if (stash('hide_opts')) {
+ <li><a href="/"><span class="visually-hidden">Hauptseite</span><i class="material-icons" aria-hidden="true">edit</i></a></li>
+ % }
+ % else {
+ <li><a href="#stationinput"><span class="visually-hidden">Menü</span><i class="material-icons" aria-hidden="true">edit</i></a></li>
+ % }
+ </ul>
+ </div>
+ </nav>
+</div>
+
+<div class="container">
+% if (my $error = stash 'error') {
+<div class="error"><strong>Fehler:</strong>
+<p>
+%= $error
+</p>
+</div>
+% }
+% elsif (stash('stationlist')) {
+<div class="error"><strong>Mehrdeutige Eingabe.</strong>
+Bitte eine Station aus der Liste auswählen</div>
+% }
+</div>
+
+<div class="content">
+%= content
+</div>
+
+% if (not stash('hide_opts')) {
+<div class="container">
+<div class="input-field">
+
+
+%= form_for _redirect => begin
+%= hidden_field efa => param('efa')
+%= hidden_field hafas => param('hafas')
+<div>
+ <div class="field">
+% if (stash('stationlist')) {
+ %= select_field input => stash('stationlist')
+% }
+% elsif (stash('input')) {
+ %= text_field 'input', class => 'station', placeholder => 'Stationsname oder Fahrtnummer', id => 'stationinput'
+% }
+% else {
+ %= text_field 'input', class => 'station', placeholder => 'Stationsname oder Fahrtnummer', id => 'stationinput', autofocus => 'autofocus'
+% }
+ </div>
+ <div class="field">
+ %= submit_button 'Abfahrtstafel'
+ </div>
+ % if (stash('input')) {
+ <div class="geolink">
+ <a class="button" href="<%= url_for('_autostop')->to_abs->scheme('https')->query({efa => param('efa'), hafas => param('hafas')}) %>">Stationen in der Umgebung suchen</a>
+ </div>
+ % }
+ <div class="backendlink">
+ <a class="button button-light" href="<%= url_for('_backend')->query({efa => param('efa'), hafas => param('hafas')}) %>">Backend: <%= param('efa') ? param('efa') . ' (EFA)' : param('hafas') ? param('hafas') . ' (HAFAS)' : 'DB (IRIS-TTS)' %></a>
+ </div>
+ <div class="break"></div>
+ <div class="moresettings-header moresettings-header-collapsed button button-light">Weitere Einstellungen</div>
+ <div class="moresettings moresettings-collapsed">
+ <div class="field">
+ <div class="desc">
+ %= check_box 'hidelowdelay' => 1, id => 'id_hidelowdelay'
+ <label for="id_hidelowdelay">
+ Verspätungen erst ab 5 Minuten anzeigen
+ </label>
+ </div>
+ </div>
+ <div class="field">
+ <div class="desc">
+ %= check_box 'detailed' => 1, id => 'id_detailed'
+ <label for="id_detailed">
+ Mehr Details
+ </label>
+ </div>
+ </div>
+ <div class="field">
+ <div class="desc">
+ %= check_box 'past' => 1, id => 'past'
+ <label for="past">
+ Fahrten der vergangenen 60 Minuten zeigen
+ </label>
+ </div>
+ </div>
+ <div class="field">
+ <div class="desc">
+ %= check_box 'hide_opts' => 1, id => 'id_hide_opts'
+ <label for="id_hide_opts">
+ Formular verstecken
+ </label>
+ </div>
+ </div>
+ <div class="field">
+ <div class="desc">
+ Nur Fahrten über
+ </div>
+ <div>
+ %= text_field 'via', placeholder => 'Bahnhof 1, Bhf2, ... (oder regulärer Ausdruck)', class => 'station'
+ </div>
+ </div>
+ <div class="field">
+ <div class="desc">
+ Gleise
+ </div>
+ <div>
+ %= text_field 'platforms', placeholder => '1, 2, 5, ...'
+ </div>
+ </div>
+ <div class="field">
+ <div class="desc">
+ Ankunfts- oder Abfahrtszeit anzeigen?
+ </div>
+ <div>
+ %= select_field admode => [['Abfahrt bevorzugen' => 'deparr'], ['Nur Abfahrt' => 'dep'], ['Nur Ankunft' => 'arr']]
+ </div>
+ </div>
+ <div class="field">
+ <div class="desc">
+ Frontend
+ </div>
+ <div>
+ %= select_field mode => [ ['App' => 'app'], ['Infoscreen' => 'infoscreen'], ['Bahnhofstafel (legacy)' => 'multi'], ['Gleisanzeiger (legacy)' => 'single'] ]
+ </div>
+ </div>
+ <div class="field">
+ %= submit_button 'Anzeigen'
+ </div>
+ </div> <!-- moresettings -->
+</div>
+% end
+
+</div> <!-- input-field -->
+
+<div class="notes">
+ <div class="developers-header developers-header-collapsed button button-light">API</div>
+ <div class="developers developers-collapsed">
+ <ul>
+ % if (0) {
+ <li>You're welcome to embed DBF departure boards as iframes or use them
+ in full-screen browser setups. The App frontend works best for
+ small screens, whereas the legacy Infoscreen mode is better suited
+ for large displays.</li>
+ <li>The departure board supports names, EVA IDs, and (in IRIS mode)
+ DS100/Ril100 codes as station identifiers.</li>
+ <li>Requests for train details can optionally be suffixed with the
+ DD.MM.[YYYY] date of the requested trip, e.g. "ICE 921 (1.1.)" or
+ "ICE 921 @ 1.1.". The date refers to the scheduled departure at the
+ train's origin station.</li>
+ <li>A JSON IRIS API is avaliable via
+ <span style="font-family: monospace;">mode=json&amp;version=3</span>
+ (or just <span style="font-family: monospace;">https://dbf.finalrewind.org/Station.json?version=3</span>).
+ Route elements may contain "isAdditional" and "isCancelled"; the rest
+ should be self-explanatory. Please do not send more than 30 requests
+ per minute and only one request per station per minute.</li>
+ <li>There is no JSON API for train details yet.</li>
+ <li>The optional <span style="font-family: monospace;">limit</span>
+ parameter limits the number of returnd departures; e.g.
+ <span style="font-family: monospace;">limit=10</span> will result in no more than ten.</li>
+ <li>DBF is available as Open Source software
+ (<a href="https://github.com/derf/db-fakedisplay/blob/master/README.md">installation instructions</a>).
+ Please use your own installation for automated crawlers that request dozens of stations per minute.</li>
+ % }
+ % else {
+ <li>DBF-Abfahrtstafeln können gerne als iframe eingebunden oder in
+ fest installierten Vollbild-Browserfenstern verwendet werden.
+ Für eine kleine Ansicht (z.B. iframe in einer normalen Website)
+ empfiehlt sich das "App"-Frontend. Für eine große Ansicht
+ (z.B. als alleinstehender Infoscreen) gibt es den "Infoscreen"-Modus.</li>
+ <li>Die Abfahrtstafel unterstützt Namen, EVA-IDs, und (im IRIS-Backend)
+ DS100/Ril100-Codes zur Identifikation von Stationen.</li>
+ <li>Abfahrten werden mit Echtzeitdaten bzw. Prognosen angegeben und
+ danach sortiert. Mit dem Parameter
+ <span style="font-family: monospace;">rt=0</span> wwerden stattdessen
+ Plandaten angegeben und zur Sortierung genutzt.</li>
+ <li>Bei HAFAS-Backends können optional Details für spezifische Fahrten im
+ DD.MM.[YYYY]-Format abgefragt werden, z.B. "ICE 921 (1.1.)" oder
+ "ICE 921 @ 1.1.". Das Datum bezieht sich auf die geplante
+ Abfahrtszeit am Startbahnhof der Fahrt.</li>
+ <li>Viele Seiten sind auch als JSON verfügbar, wahlweise mittels
+ <span style="font-family: monospace;">Accept: application/json</span> oder
+ durch <span style="font-family: monospace;">.json</span> in der URL.
+ HAFAS- und IRIS-Abfahrtstafeln liefern mit dem GET-Parameter <span style="font-family: monospace;">version=3</span> eine stabile JSON-API.
+ Alle anderen Endpunkte (sowie Abfahrtstafeln mit <span style="font-family: monospace;">version=raw</span>) erlauben direkten Zugriff auf die serialisierten Travel::Status::DE::{EFA,HAFAS,IRIS}-Objekte ohne stabile API.</li>
+ <li>Bitte maximal 30 Anfragen pro Minute und insbesondere nur eine Anfrage
+ pro Station und Minute – eine höhere Auflösung haben die Backenddaten
+ ohnehin nicht.</li>
+ <li>Mit <span style="font-family: monospace;">limit</span> kann die Anzahl der
+ angezeigten / im JSON enthaltenen Abfahrten eingeschränkt werden, z.B.
+ <span style="font-family: monospace;">limit=10</span> für die ersten zehn.</li>
+ <li>Dieser Dienst ist Open Source-Software und kann leicht auf eigenen Servern
+ <a href="https://github.com/derf/db-fakedisplay/blob/master/README.md">installiert</a>
+ werden. Automatisierte Crawler, die mehrere Dutzend Stationen pro Minute
+ abfragen, bitte nur auf eigenen Instanzen betreiben.</li>
+ % }
+ </ul>
+ </div> <!-- developers -->
+</div> <!-- notes -->
+
+</div> <!-- container -->
+
+<div class="container">
+<div class="config">
+Farbschema:
+<a onClick="javascript:setTheme('light')">hell</a>
+<a onClick="javascript:setTheme('dark')">dunkel</a>
+<a onClick="javascript:setTheme('default')">automatisch</a>
+<!--Language:
+<br/>
+<a onClick="javascript:setLang('de')">DE</a>
+<a onClick="javascript:setLang('en')">EN</a>
+<a onClick="javascript:setLang('default')">system language</a>
+-->
+</div> <!-- config -->
+</div> <!-- container -->
+% }
+% if (not stash('hide_footer')) {
+<div class="container">
+<div class="about">
+<a href="_about">DBF</a> v<%= stash('version') // '???' %>
+<a href="_datenschutz" rel="nofollow">Datenschutz</a>
+<a href="_impressum" rel="nofollow">Impressum</a>
+</div> <!-- about -->
+</div> <!-- container -->
+% }
+
+</body>
+</html>
diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep
deleted file mode 100644
index a169699..0000000
--- a/templates/layouts/default.html.ep
+++ /dev/null
@@ -1,193 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title><%= stash('title') // 'db-infoscreen' %></title>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
-% if ($self->stash('refresh_interval')) {
- <meta http-equiv="refresh" content="<%= $self->stash('refresh_interval') %>"/>
-% }
-
-
- %= stylesheet '/default.css'
-% if ($self->browser->mobile) {
- %= stylesheet '/mobile.css'
-% }
- %= javascript '/jquery-1.10.2.min.js'
- %= javascript '/collapse.js'
- %= javascript '/marquee.js'
- %= javascript begin
- $(function () { $('marquee').marquee() });
- % end
-</head>
-<body style="<%= (stash('hide_opts') ? 'margin: 0; padding: 0;' : q{}) %>">
-
-% if (my $error = stash 'error') {
-<div class="error">Backend-Fehler:</div>
-<div>
-<pre>
-%= $error
-</pre>
-</div>
-% }
-
-% if (time() < 1420178400 and param('backend') and (param('backend') eq 'iris' ) ) {
-<div class="error">Note: Due to an unknown problem on the Deutsche Bahn IRIS
-server, no schedule data is available. This service will resume operation
-on 2015-01-02 at 07:00 CET.</div>
-% }
-
-%= content
-
-% if (stash 'show_intro') {
-<p>
- Diese Seite ist ein Frontend zum IRIS (Abfahrtsmonitor)
- der Deutschen Bahn. Sie kann wahlweise die in Bahnhofshallen / an
- Haltestellen montierten Anzeigen nachahmen oder einen Handy- und
- Infoscreen-taugliche Abfahrtsmonitor liefern.
-</p>
-<p>
- Als Backend werden sowohl das RIS ("Reisenden-Informationssystem", d.h. der
- Bahn-Abfahrtsmonitor) als auch das IRIS ("Innerbetriebliches
- Reisenden-Informationssystem"?) unterstützt. Im Normalfall enthält das
- IRIS mehr und detailliertere Angaben.
-</p>
-% }
-
-
-% if (not stash('hide_opts')) {
-<div class="input-field">
-
-
-%= form_for _redirect => begin
-<div>
- <div class="field">
- <div class="desc">Bahnhof / Haltestelle</div>
- <div>
-% if (stash('stationlist')) {
- %= select_field station => stash('stationlist')
-% }
-% else {
- %= text_field 'station'
-% }
- %= submit_button 'Display'
- </div>
- </div>
- <div class="break"></div>
- <span class="optional">Optionale Einstellungen:</span>
- <div class="field">
- <div class="desc">
- Nur Züge über
- </div>
- <div>
- %= text_field 'via'
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Gleise
- </div>
- <div>
- %= text_field 'platforms'
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Verspätungen &lt;5 Minuten ignorieren
- </div>
- <div>
- %= check_box 'hidelowdelay' => 1
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Layout
- </div>
- <div>
- %= select_field mode => [ ['Infoscreen' => 'clean'], ['Bahnhofstafel' => 'multi'], ['Gleis' => 'single'] ]
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Backend
- </div>
- <div>
- %= select_field backend => [ ['IRIS' => 'iris'], ['RIS' => 'ris'] ]
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Formular verstecken
- </div>
- <div>
- %= check_box 'hide_opts' => 1
- </div>
- </div>
- <div class="break"></div>
- <span class="optional">Nur für IRIS-Backend:</span>
- <div class="field">
- <div class="desc">
- Ankunfts- oder Abfahrtszeiten
- </div>
- <div>
- %= select_field admode => [['Abfahrt bevorzugen' => 'deparr'], ['Nur Abfahrt' => 'dep'], ['Nur Ankunft' => 'arr']]
- </div>
- </div>
- <div class="field">
- <div class="desc">
- Erwartete Zeiten statt Fahrplandaten
- </div>
- <div>
- %= check_box 'show_realtime' => 1
- </div>
- </div>
-</div>
-% end
-
-</div> <!-- input-field -->
-
-<div class="notes">
-<span class="notes">Anmerkungen:</span>
-<ul>
-<li>Umlaute werden nicht bei allen Backend/Layout-Kombinationen korrekt unterstützt.
-Wenn etwas nicht funktioniert helfen meist
-Umschreibungen der Art ä → ae oder ä → a</li>
-<li>Das RIS-Backend ist deprecated und wird bald ausgetauscht oder entfernt</li>
-</ul>
-</div> <!-- notes -->
-
-<div class="notes">
-<span class="notes">Beispiele:</span>
-<ul>
-<li><a href="/Essen%20Hbf?mode=multi">Essen HBf</a> (IRIS, Bahnhofstafel)</li>
-<li><a href="/Dortmund/Bochum%7CHamm?mode=multi">Dortmund HBf</a> (IRIS, Bahnhofstafel,
-nur Züge via Bochum oder Hamm)</li>
-<li><a href="/Dortmund%20Uni?mode=clean">Dortmund Universit&auml;t</a> (IRIS, Mobile/Infoscreen)</li>
-<li><a href="/KD?mode=single">D&uuml;sseldorf HBf</a> (IRIS, Gleistafel)</li>
-</ul>
-</div>
-
-<div class="notes">
-<span class="notes">Siehe auch:</span>
-<ul>
-<li><a href="http://reiseauskunft.bahn.de/bin/bhftafel.exe/dn">DeutscheBahn RIS</a></li>
-<li><a href="http://marudor.de/">Handyfreundliches Interface</a> von <a href="https://twitter.com/marudor">@marudor</a></li>
-<li><a href="https://iris.noncd.db.de/wbt/js/index.html?typ=ab&amp;style=qrab&amp;bhf=EE&amp;SecLang=&amp;Zeilen=40&amp;footer=0&amp;disrupt=1"
->DeutscheBahn IRIS</a> und <a href="http://www.db-netz.de/file/2361656/data/betriebsstellen.pdf">betriebsstellen.pdf</a></li>
-<li><a href="http://vrrf.finalrewind.org/">vrr-infoscreen</a></li>
-</ul>
-</div> <!-- notes -->
-
-<div class="about">
-<a href="http://finalrewind.org/projects/db-fakedisplay/">db-infoscreen</a>
-v<%= stash('version') // '???' %><br/>
-Backends:<br/>
-<a href="http://finalrewind.org/projects/Travel-Status-DE-DeutscheBahn/">Travel::Status::DE::HAFAS</a>
-v<%= $Travel::Status::DE::HAFAS::VERSION %><br/>
-<a href="http://finalrewind.org/projects/Travel-Status-DE-IRIS/">Travel::Status::DE::IRIS</a>
-v<%= $Travel::Status::DE::IRIS::VERSION %><br/>
-</div>
-% }
-
-</body>
-</html>
diff --git a/templates/layouts/legacy.html.ep b/templates/layouts/legacy.html.ep
new file mode 100644
index 0000000..e7e59ec
--- /dev/null
+++ b/templates/layouts/legacy.html.ep
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+ <title><%= stash('title') // 'DBF' %></title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="keywords" content="Abfahrtsmonitor, Bahnhofstafel, Abfahrten, Abfahrtstafel, ICE, IC, RE, RB, S-Bahn">
+ <meta name="description" content="Inoffizieller Abfahrtsmonitor für Bahnhöfe der DB">
+ <link rel="icon" type="image/png" href="/static/icons/icon-16x16.png" sizes="16x16">
+ <link rel="icon" type="image/png" href="/static/icons/icon-32x32.png" sizes="32x32">
+ <link rel="icon" type="image/png" href="/static/icons/icon-96x96.png" sizes="96x96">
+ <link rel="apple-touch-icon" href="/static/icons/icon-120x120.png">
+ <link rel="apple-touch-icon" sizes="180x180" href="/static/icons/icon-180x180.png">
+ <link rel="apple-touch-icon" sizes="152x152" href="/static/icons/icon-152x152.png">
+ <link rel="apple-touch-icon" sizes="167x167" href="/static/icons/icon-167x167.png">
+% if ($self->stash('refresh_interval')) {
+ <meta http-equiv="refresh" content="<%= $self->stash('refresh_interval') %>"/>
+% }
+
+ % my $av = 'v110'; # asset version
+ %= stylesheet "/static/${av}/css/legacy.css"
+ %= stylesheet "/static/${av}/css/material-icons.css"
+ %= stylesheet "/static/${av}/css/jquery-ui.min.css"
+% my $force_mobile = param('force_mobile') // stash('force_mobile');
+% if ($force_mobile) {
+ %= stylesheet "/static/${av}/css/legacy-mobile.css"
+% }
+ %if (stash('load_marquee')) {
+ %= javascript '/static/js/jquery-3.4.1.min.js'
+ %= javascript "/static/${av}/js/jquery-ui.min.js"
+ %= javascript "/static/${av}/js/dbf.min.js"
+ % if (not stash('hide_opts')) {
+ %= javascript "/dyn/${av}/autocomplete.js", defer => undef
+ % }
+ %= javascript "/static/${av}/js/marquee.min.js"
+ %= javascript begin
+ $(function () { $('marquee').marquee() });
+ % end
+ % } else {
+ %= javascript '/static/js/jquery-3.4.1.min.js', defer => undef
+ %= javascript "/static/${av}/js/jquery-ui.min.js", defer => undef
+ %= javascript "/static/${av}/js/dbf.min.js", defer => undef
+ % }
+</head>
+<body style="<%= (param('dark') ? 'background-color: #000000; color: #ffffff;' : q{}) %>">
+
+<div class="container">
+% if (my $error = stash 'error') {
+<div class="error"><strong>Backend-Fehler:</strong>
+<pre>
+%= $error
+</pre>
+</div>
+% }
+% elsif (stash('stationlist')) {
+<div class="error"><strong>Mehrdeutige Eingabe.</strong>
+Bitte eine Station aus der Liste auswählen</div>
+% }
+</div>
+
+<div class="content">
+%= content
+</div>
+
+</body>
+</html>
diff --git a/templates/multi.html.ep b/templates/multi.html.ep
index 704c589..0095957 100644
--- a/templates/multi.html.ep
+++ b/templates/multi.html.ep
@@ -48,3 +48,9 @@
</div> <!-- displaymulti -->
% }
+
+<p class="notice">
+<strong>Deprecation Warning.</strong>
+Dieses Frontend wird nicht mehr weitergewickelt und möglicherweise in einer
+zukünftigen DBF-Version entfernt.
+</p>
diff --git a/templates/not_found.html.ep b/templates/not_found.html.ep
index 5886148..c03d32d 100644
--- a/templates/not_found.html.ep
+++ b/templates/not_found.html.ep
@@ -1,9 +1,12 @@
-<div class="error">404 Page Not Found</div>
-<div>
-<pre>
+<div class="container">
+<div class="error">
+<strong>404 Page Not Found:</strong>
+% if (my $e = stash('message')) {
+%= $e
+% }
+% else {
Die aufgerufene URL existiert nicht und ist keine gültige Abbildung auf einen
Bahnhofsnamen.
-
-The requested URL does not exist and does not map to a train station.
-</pre>
+% }
+</div>
</div>
diff --git a/templates/route_map.html.ep b/templates/route_map.html.ep
new file mode 100644
index 0000000..e1c4642
--- /dev/null
+++ b/templates/route_map.html.ep
@@ -0,0 +1,98 @@
+% if (stash('origin') and stash('destination')) {
+ %= include '_map_infobox'
+% }
+
+<div class="container">
+ <div id="map" style="height: 70vh;">
+ </div>
+ </div>
+
+<script>
+var map = L.map('map').setView([51.306, 9.712], 6);
+
+L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+}).addTo(map);
+var stations = [
+% for my $station ( @{stash('station_coordinates') // [] } ) {
+[[<%= $station->[0][0] %>,<%= $station->[0][1] %>],['<%== join("','", map { Mojo::Util::xml_escape($_) } @{$station->[1]}) %>']],
+% }
+];
+
+var routes = [];
+var pl;
+% for my $line_group ( @{ stash('polyline_groups') // [] } ) {
+ routes = [
+ % for my $pair ( @{$line_group->{polylines} // []} ) {
+ [[<%= $pair->[0][0] %>,<%= $pair->[0][1] %>],[<%= $pair->[1][0] %>,<%= $pair->[1][1] %>]],
+ % }
+ ];
+ pl = L.polyline(routes, {color: '<%= $line_group->{color} %>', opacity: <%= $line_group->{opacity} %>}).addTo(map);
+ % if ($line_group->{fit_bounds}) {
+ if (routes.length) {
+ map.fitBounds(pl.getBounds());
+ }
+ % }
+% }
+
+for (var station_id in stations) {
+ L.circle(stations[station_id][0], {
+ color: '#f03',
+ opacity: 0.7,
+ fillColor: '#f03',
+ fillOpacity: 0.5,
+ radius: <%= stash('station_radius') || 250 %>
+ }).bindPopup(stations[station_id][1].join('<br/>')).addTo(map);
+}
+
+var greenIcon = new L.Icon({
+ iconUrl: '/static/leaflet/images/marker-icon-2x-green.png',
+ shadowUrl: '/static/leaflet/images/marker-shadow.png',
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ shadowSize: [41, 41]
+});
+
+var goldIcon = new L.Icon({
+ iconUrl: '/static/leaflet/images/marker-icon-2x-gold.png',
+ shadowUrl: '/static/leaflet/images/marker-shadow.png',
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ shadowSize: [41, 41]
+});
+
+var marker;
+% for my $marker (@{stash('markers') // [] } ) {
+ % if ($marker->{icon}) {
+ marker = L.marker([<%= $marker->{lat} %>,<%= $marker->{lon} %>], {icon: <%= $marker->{icon} %>}).addTo(map);
+ % }
+ % else {
+ marker = L.marker([<%= $marker->{lat} %>,<%= $marker->{lon} %>]).addTo(map);
+ % }
+ % if ($marker->{title}) {
+ marker.bindPopup('<%= $marker->{title} %>');
+ % }
+% }
+
+</script>
+
+<div class="container" style="margin-top: 1ex; margin-bottom: 1ex; color: #555;">
+<p>
+Die eingezeichnete Route stammt aus dem angefragten Backend und stimmt nicht
+notwendigerweise mit der Realität überein.
+Die Fahrzeugposition auf der Karte ist eine DBF-eigene Schätzung und kann
+erheblich von den tatsächlichen Gegebenheiten abweichen.
+% if (stash('intersection')) {
+<br/>In dieser Ansicht sind Live-Updates der Zug- und Begegnungspositionen noch
+nicht implementiert.
+% }
+</p>
+</div>
+
+% if (my $op = stash('operator')) {
+<div class="container" style="margin-top: 1ex; margin-bottom: 1ex; color: #555;">
+<p>Betrieb: <%= $op %></p>
+</div>
+% }
diff --git a/templates/select_backend.html.ep b/templates/select_backend.html.ep
new file mode 100644
index 0000000..c6d2a4c
--- /dev/null
+++ b/templates/select_backend.html.ep
@@ -0,0 +1,46 @@
+<div class="container">
+ <p>
+ Das Backend bestimmt die Datenquelle für Stations- und Zuginformationen.
+ Innerhalb Deutschlands ist <strong>Deutsche Bahn</strong> via IRIS-TTS eine gute Wahl für Schienenverkehr im Bahnnetz.
+ Die anderen Backends bieten sich für Fahrten im zugehörigen Verkehrsverbund (inklusive Nahverkehr) sowie im Ausland an.
+ Sofern bekannt sind unterhalb der Backend-Namen Karten verlinkt, die die ungefähre Abdeckung aufzeigen.
+ Ein Backend, welches Nah- und Fernverkehr in ganz Deutschland abdeckt, ist aktuell leider nicht verfügbar.
+ </p>
+ <p>
+ % my $prev_type = 'IRIS-TTS';
+ % for my $backend (@{$backends}) {
+ <p>
+ % if ($backend->{type} ne $prev_type) {
+ % $prev_type = $backend->{type};
+ <%= $prev_type %>:<br/>
+ % }
+ % my $class = 'button';
+ % if (param('efa')) {
+ % if ($backend->{efa} and $backend->{shortname} eq param('efa')) {
+ % $class .= ' button-active';
+ % }
+ % }
+ % elsif (param('hafas')) {
+ % if ($backend->{hafas} and $backend->{shortname} eq param('hafas')) {
+ % $class .= ' button-active';
+ % }
+ % }
+ % else {
+ % if (not ($backend->{efa} or $backend->{hafas})) {
+ % $class .= ' button-active';
+ % }
+ % }
+ <a class="<%= $class %>" href="<%= url_for(q{/})->query({ efa => $backend->{efa} ? $backend->{shortname} : q{}, hafas => $backend->{hafas} ? $backend->{shortname} : q{} }) %>"><%= $backend->{shortname} // 'IRIS-TTS' %> – <%= $backend->{name} %></a>
+ % if ($backend->{has_area}) {
+ <a href="/coverage/<%= $backend->{type} %>/<%= $backend->{shortname} %>"><%= join(q{, }, @{$backend->{regions}}) || '[Karte]' %></a>
+ % }
+ % else {
+ %= join(q{, }, @{$backend->{regions} // []})
+ % }
+ % if ($backend->{homepage}) {
+ (<a href="<%= $backend->{homepage} %>"><%= $backend->{homepage} =~ s{ ^ http s? :// (?: www[.] )? (.*?) (?: / )? $ }{$1}xr %></a>)
+ % }
+ </p>
+ % }
+ </p>
+</div>
diff --git a/templates/single.html.ep b/templates/single.html.ep
index a4e370c..0156bf4 100644
--- a/templates/single.html.ep
+++ b/templates/single.html.ep
@@ -1,3 +1,9 @@
+<p class="notice">
+<strong>Deprecation Warning.</strong>
+Dieses Frontend wird nicht mehr weitergewickelt und möglicherweise in einer
+zukünftigen DBF-Version abgeschaltet.
+</p>
+
% if (@{$departures}) {
<div class="displaysingle">
@@ -6,7 +12,7 @@
% $i++;
<div class="display">
<div class="platform">
-%= $departure->{platform}
+%= numeric_platform_part($departure->{platform})
</div>
<div class="time">
%= $departure->{time}
diff --git a/templates/train_details.html.ep b/templates/train_details.html.ep
new file mode 100644
index 0000000..7d5ea90
--- /dev/null
+++ b/templates/train_details.html.ep
@@ -0,0 +1,5 @@
+<div class="app" data-station="<%= stash('station_name') // q{} %>">
+<div class="moreinfo" data-static="1">
+ %= include '_train_details'
+</div>
+</div>
diff --git a/templates/wagen.html.ep b/templates/wagen.html.ep
new file mode 100644
index 0000000..efc2e32
--- /dev/null
+++ b/templates/wagen.html.ep
@@ -0,0 +1,75 @@
+<div class="container singlewagon">
+ % if (not $wref->{e} and $wref->{s} and $wref->{p} and $wref->{ws}) {
+ <p>
+ <%= $wref->{s} %> Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
+ </p>
+ % }
+ % if ($wref->{e} eq 'u') {
+ % if ($wref->{s} and $wref->{p} and $wref->{ws}) {
+ <div class="platform">
+ <%= $wref->{s} %><br/>Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
+ <div class="sign-left"><i class="material-icons">arrow_upward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_upward</i></div>
+ </div>
+ % }
+ % }
+ % elsif (defined $wref->{d} and $wref->{e} ne 'u') {
+ % if ($wref->{d} == 0) {
+ <div class="sign-left"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_back</i></div>
+ % }
+ % else {
+ <div class="sign-left"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_forward</i></div>
+ % }
+ % }
+ % for my $wagon_file (@{$wagon_files // [] }) {
+ % if ($wagon_file ne $wagon_files->[0] and defined $wref->{d}) {
+ % if ($wref->{d} == 0) {
+ <div class="sign-left"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_back</i></div>
+ % }
+ % else {
+ <div class="sign-left"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_forward</i></div>
+ % }
+ % }
+ <div style="clear: both;">
+ <a href="<%= $wagon_file %>"><img class="wagonfile" src="<%= $wagon_file %>"></a>
+ </div>
+ % }
+ % if ($wref->{e} eq 'd') {
+ <div class="sign-left"><i class="material-icons">arrow_downward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_downward</i></div>
+ % if ($wref->{s} and $wref->{p} and $wref->{ws}) {
+ <div class="platform">
+ <%= $wref->{s} %><br/>Gleis <%= $wref->{p} %> <%= $wref->{ws} %>
+ </div>
+ % }
+ % }
+ % elsif (defined $wref->{d} and $wref->{e} ne 'd') {
+ % if ($wref->{d} == 0) {
+ <div class="sign-left"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_back</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_back</i></div>
+ % }
+ % else {
+ <div class="sign-left"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-right"><i class="material-icons">arrow_forward</i></div>
+ <div class="sign-center"><i class="material-icons">arrow_forward</i></div>
+ % }
+ % }
+ % if ($wagon_data->{name}) {
+ <p>
+ <%= $wagon_data->{name} %>
+ </p>
+ % }
+ <p class="copyright">
+ Abbildung © Seemanngrafik d.i.p. im Auftrag der Deutschen Bahn AG,
+ lizensiert unter CC-BY-4.0
+ </p>
+</div>
diff --git a/templates/wagenreihung.html.ep b/templates/wagenreihung.html.ep
new file mode 100644
index 0000000..19c49ab
--- /dev/null
+++ b/templates/wagenreihung.html.ep
@@ -0,0 +1,57 @@
+% if (not $wr or $wr_error) {
+ <div class="container">
+ <div class="error">
+ <strong>Fehler bei der Abfrage der Wagenreihung:</strong>
+ <%= $wr_error // 'Unbekannter Fehler' %>
+ </div>
+ </div>
+% }
+% else {
+ <div class="container">
+ <div style="text-align: center;">
+ Gleis <%= $wr->platform %><br/>
+ </div>
+ </div>
+ <div class="container">
+ <div class="wagonorder exit-<%= stash('exit_dir') // 'unknown'%>">
+% for my $sector ($wr->sectors) {
+ <div class="section" style="
+ top: <%= $sector->start_percent %>%; bottom: <%= 100 - $sector->end_percent %>%;">
+%= $sector->name
+ </div>
+% }
+% for my $group ($wr->groups) {
+% my $first = 1;
+% for my $wagon ($group->carriages) {
+%= include '_wagon', wr => $wr, group => $group, wagon => $wagon, first => $first, multi => (scalar $wr->destinations) - 1 + (scalar $wr->train_numbers) - 1, wref => $wref, exit_dir => stash('exit_dir'), train_no => param('number');
+% $first = 0;
+% }
+% }
+ </div>
+ % for my $group ($wr->groups) {
+ % if ($group->description) {
+ <div style="text-align: center;">
+ %= $group->description
+ % if ($group->designation) {
+ „<%= $group->designation %>“
+ % }
+ % if (scalar $wr->groups > 1 and $group->has_sectors) {
+ in Abschnitt <%= join(q{}, sort $group->sectors) %>
+ % }
+ </div>
+ % }
+ % }
+ <div style="text-align: center;">
+ nach
+%= join( ' / ', map { $_->{name} } $wr->destinations )
+ </div>
+<!-- <div>
+ Legende: ♿ Behindertengerechte Ausstattung / 🍴 Bistro/Restaurant / 🚪 Abteile vorhanden
+ </div>
+-->
+ <p class="copyright">
+ Quelle: DB Wagenreihungs-API (<%= stash('ts') // q{} %>). Angaben ohne Gewähr.
+ </p>
+
+ </div>
+% }
diff --git a/templates/zugbildung_db.html.ep b/templates/zugbildung_db.html.ep
new file mode 100644
index 0000000..45e52aa
--- /dev/null
+++ b/templates/zugbildung_db.html.ep
@@ -0,0 +1,26 @@
+% if ($wr_error) {
+ <div class="container">
+ <div class="error">
+ <strong>Fehler bei der Abfrage der Wagenreihung:</strong>
+ <%= $wr_error %>
+ </div>
+ </div>
+% }
+<div class="container">
+ <div style="text-align: center;"><%= $route %></div>
+ Vorgesehener Zugtyp: <%= $zb->{type} %>
+</div>
+<div class="container">
+ <div class="wagonorder exit-unknown">
+% for my $wagon (@{$wagons // []}) {
+%= include '_wagon', direction => undef, wagon => $wagon, type => $zb->{type}, wref => '', exit_dir => 'unknown';
+% }
+ </div>
+ <p class="copyright">
+ Quelle: <a href="https://data.deutschebahn.com/dataset/zugbildungsplanzugbildungsplan-zpar">DB Zugbildungsplan</a>
+ mit <a href="https://github.com/derf/db-zugbildung-to-json">automatisierter Nachbearbeitung</a>.<br/>
+ Nachbearbeitungsbedingte Fehler sind wahrscheinlich.<br/>
+ Daten © 2020 DB Fernverkehr AG, lizensiert unter CC-BY 4.0.
+ </p>
+
+ </div>